anvil/eth/backend/
db.rs

1//! Helper types for working with [revm](foundry_evm::revm)
2
3use std::{
4    collections::BTreeMap,
5    fmt::{self, Debug},
6    path::Path,
7};
8
9use alloy_consensus::{BlockBody, Header};
10use alloy_primitives::{
11    Address, B256, Bytes, U256, keccak256,
12    map::{AddressMap, HashMap},
13};
14use alloy_rpc_types::BlockId;
15use anvil_core::eth::{
16    block::Block,
17    transaction::{MaybeImpersonatedTransaction, TransactionInfo},
18};
19use foundry_common::errors::FsPathError;
20use foundry_evm::backend::{
21    BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, StateSnapshot,
22};
23use foundry_primitives::{FoundryReceiptEnvelope, FoundryTxEnvelope};
24use revm::{
25    Database, DatabaseCommit,
26    bytecode::Bytecode,
27    context::BlockEnv,
28    context_interface::block::BlobExcessGasAndPrice,
29    database::{CacheDB, DatabaseRef, DbAccount},
30    primitives::{KECCAK_EMPTY, eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
31    state::AccountInfo,
32};
33use serde::{
34    Deserialize, Deserializer, Serialize,
35    de::{Error as DeError, MapAccess, Visitor},
36};
37use serde_json::Value;
38
39use crate::mem::storage::MinedTransaction;
40
41/// Helper trait get access to the full state data of the database
42pub trait MaybeFullDatabase: DatabaseRef<Error = DatabaseError> + Debug {
43    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
44        None
45    }
46
47    /// Clear the state and move it into a new `StateSnapshot`.
48    fn clear_into_state_snapshot(&mut self) -> StateSnapshot;
49
50    /// Read the state snapshot.
51    ///
52    /// This clones all the states and returns a new `StateSnapshot`.
53    fn read_as_state_snapshot(&self) -> StateSnapshot;
54
55    /// Clears the entire database
56    fn clear(&mut self);
57
58    /// Reverses `clear_into_snapshot` by initializing the db's state with the state snapshot.
59    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot);
60}
61
62impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T
63where
64    &'a T: DatabaseRef<Error = DatabaseError>,
65{
66    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
67        T::maybe_as_full_db(self)
68    }
69
70    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
71        unreachable!("never called for DatabaseRef")
72    }
73
74    fn read_as_state_snapshot(&self) -> StateSnapshot {
75        unreachable!("never called for DatabaseRef")
76    }
77
78    fn clear(&mut self) {}
79
80    fn init_from_state_snapshot(&mut self, _state_snapshot: StateSnapshot) {}
81}
82
83/// Helper trait to reset the DB if it's forked
84pub trait MaybeForkedDatabase {
85    fn maybe_reset(&mut self, _url: Option<String>, block_number: BlockId) -> Result<(), String>;
86
87    fn maybe_flush_cache(&self) -> Result<(), String>;
88
89    fn maybe_inner(&self) -> Result<&BlockchainDb, String>;
90}
91
92/// This bundles all required revm traits
93pub trait Db:
94    DatabaseRef<Error = DatabaseError>
95    + Database<Error = DatabaseError>
96    + DatabaseCommit
97    + MaybeFullDatabase
98    + MaybeForkedDatabase
99    + fmt::Debug
100    + Send
101    + Sync
102{
103    /// Inserts an account
104    fn insert_account(&mut self, address: Address, account: AccountInfo);
105
106    /// Sets the nonce of the given address
107    fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<()> {
108        let mut info = self.basic(address)?.unwrap_or_default();
109        info.nonce = nonce;
110        self.insert_account(address, info);
111        Ok(())
112    }
113
114    /// Sets the balance of the given address
115    fn set_balance(&mut self, address: Address, balance: U256) -> DatabaseResult<()> {
116        let mut info = self.basic(address)?.unwrap_or_default();
117        info.balance = balance;
118        self.insert_account(address, info);
119        Ok(())
120    }
121
122    /// Sets the code of the given address
123    fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> {
124        let mut info = self.basic(address)?.unwrap_or_default();
125        let code_hash = if code.as_ref().is_empty() {
126            KECCAK_EMPTY
127        } else {
128            B256::from_slice(&keccak256(code.as_ref())[..])
129        };
130        info.code_hash = code_hash;
131        info.code = Some(Bytecode::new_raw(alloy_primitives::Bytes(code.0)));
132        self.insert_account(address, info);
133        Ok(())
134    }
135
136    /// Sets the storage value at the given slot for the address
137    fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()>;
138
139    /// inserts a blockhash for the given number
140    fn insert_block_hash(&mut self, number: U256, hash: B256);
141
142    /// Write all chain data to serialized bytes buffer
143    fn dump_state(
144        &self,
145        at: BlockEnv,
146        best_number: u64,
147        blocks: Vec<SerializableBlock>,
148        transactions: Vec<SerializableTransaction>,
149        historical_states: Option<SerializableHistoricalStates>,
150    ) -> DatabaseResult<Option<SerializableState>>;
151
152    /// Deserialize and add all chain data to the backend storage
153    fn load_state(&mut self, state: SerializableState) -> DatabaseResult<bool> {
154        for (addr, account) in state.accounts.into_iter() {
155            let old_account_nonce = DatabaseRef::basic_ref(self, addr)
156                .ok()
157                .and_then(|acc| acc.map(|acc| acc.nonce))
158                .unwrap_or_default();
159            // use max nonce in case account is imported multiple times with difference
160            // nonces to prevent collisions
161            let nonce = std::cmp::max(old_account_nonce, account.nonce);
162
163            self.insert_account(
164                addr,
165                AccountInfo {
166                    balance: account.balance,
167                    code_hash: KECCAK_EMPTY, // will be set automatically
168                    code: if account.code.0.is_empty() {
169                        None
170                    } else {
171                        Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0)))
172                    },
173                    nonce,
174                    account_id: None,
175                },
176            );
177
178            for (k, v) in account.storage.into_iter() {
179                self.set_storage_at(addr, k, v)?;
180            }
181        }
182        Ok(true)
183    }
184
185    /// Creates a new state snapshot.
186    fn snapshot_state(&mut self) -> U256;
187
188    /// Reverts a state snapshot.
189    ///
190    /// Returns `true` if the state snapshot was reverted.
191    fn revert_state(&mut self, state_snapshot: U256, action: RevertStateSnapshotAction) -> bool;
192
193    /// Returns the state root if possible to compute
194    fn maybe_state_root(&self) -> Option<B256> {
195        None
196    }
197
198    /// Returns the current, standalone state of the Db
199    fn current_state(&self) -> StateDb;
200}
201
202/// Convenience impl only used to use any `Db` on the fly as the db layer for revm's CacheDB
203/// This is useful to create blocks without actually writing to the `Db`, but rather in the cache of
204/// the `CacheDB` see also
205/// [Backend::pending_block()](crate::eth::backend::mem::Backend::pending_block())
206impl<T: DatabaseRef<Error = DatabaseError> + Send + Sync + Clone + fmt::Debug> Db for CacheDB<T> {
207    fn insert_account(&mut self, address: Address, account: AccountInfo) {
208        self.insert_account_info(address, account)
209    }
210
211    fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> {
212        self.insert_account_storage(address, slot.into(), val.into())
213    }
214
215    fn insert_block_hash(&mut self, number: U256, hash: B256) {
216        self.cache.block_hashes.insert(number, hash);
217    }
218
219    fn dump_state(
220        &self,
221        _at: BlockEnv,
222        _best_number: u64,
223        _blocks: Vec<SerializableBlock>,
224        _transaction: Vec<SerializableTransaction>,
225        _historical_states: Option<SerializableHistoricalStates>,
226    ) -> DatabaseResult<Option<SerializableState>> {
227        Ok(None)
228    }
229
230    fn snapshot_state(&mut self) -> U256 {
231        U256::ZERO
232    }
233
234    fn revert_state(&mut self, _state_snapshot: U256, _action: RevertStateSnapshotAction) -> bool {
235        false
236    }
237
238    fn current_state(&self) -> StateDb {
239        StateDb::new(MemDb::default())
240    }
241}
242
243impl<T: DatabaseRef<Error = DatabaseError> + Debug> MaybeFullDatabase for CacheDB<T> {
244    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
245        Some(&self.cache.accounts)
246    }
247
248    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
249        let db_accounts = std::mem::take(&mut self.cache.accounts);
250        let mut accounts = HashMap::default();
251        let mut account_storage = HashMap::default();
252
253        for (addr, mut acc) in db_accounts {
254            account_storage.insert(addr, std::mem::take(&mut acc.storage));
255            let mut info = acc.info;
256            info.code = self.cache.contracts.remove(&info.code_hash);
257            accounts.insert(addr, info);
258        }
259        let block_hashes = std::mem::take(&mut self.cache.block_hashes);
260        StateSnapshot { accounts, storage: account_storage, block_hashes }
261    }
262
263    fn read_as_state_snapshot(&self) -> StateSnapshot {
264        let mut accounts = HashMap::default();
265        let mut account_storage = HashMap::default();
266
267        for (addr, acc) in &self.cache.accounts {
268            account_storage.insert(*addr, acc.storage.clone());
269            let mut info = acc.info.clone();
270            info.code = self.cache.contracts.get(&info.code_hash).cloned();
271            accounts.insert(*addr, info);
272        }
273
274        let block_hashes = self.cache.block_hashes.clone();
275        StateSnapshot { accounts, storage: account_storage, block_hashes }
276    }
277
278    fn clear(&mut self) {
279        self.clear_into_state_snapshot();
280    }
281
282    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
283        let StateSnapshot { accounts, mut storage, block_hashes } = state_snapshot;
284
285        for (addr, mut acc) in accounts {
286            if let Some(code) = acc.code.take() {
287                self.cache.contracts.insert(acc.code_hash, code);
288            }
289            self.cache.accounts.insert(
290                addr,
291                DbAccount {
292                    info: acc,
293                    storage: storage.remove(&addr).unwrap_or_default(),
294                    ..Default::default()
295                },
296            );
297        }
298        self.cache.block_hashes = block_hashes;
299    }
300}
301
302impl<T: DatabaseRef<Error = DatabaseError>> MaybeForkedDatabase for CacheDB<T> {
303    fn maybe_reset(&mut self, _url: Option<String>, _block_number: BlockId) -> Result<(), String> {
304        Err("not supported".to_string())
305    }
306
307    fn maybe_flush_cache(&self) -> Result<(), String> {
308        Err("not supported".to_string())
309    }
310
311    fn maybe_inner(&self) -> Result<&BlockchainDb, String> {
312        Err("not supported".to_string())
313    }
314}
315
316/// Represents a state at certain point
317#[derive(Debug)]
318pub struct StateDb(pub(crate) Box<dyn MaybeFullDatabase + Send + Sync>);
319
320impl StateDb {
321    pub fn new(db: impl MaybeFullDatabase + Send + Sync + 'static) -> Self {
322        Self(Box::new(db))
323    }
324
325    pub fn serialize_state(&mut self) -> StateSnapshot {
326        // Using read_as_snapshot makes sures we don't clear the historical state from the current
327        // instance.
328        self.read_as_state_snapshot()
329    }
330}
331
332impl DatabaseRef for StateDb {
333    type Error = DatabaseError;
334    fn basic_ref(&self, address: Address) -> DatabaseResult<Option<AccountInfo>> {
335        self.0.basic_ref(address)
336    }
337
338    fn code_by_hash_ref(&self, code_hash: B256) -> DatabaseResult<Bytecode> {
339        self.0.code_by_hash_ref(code_hash)
340    }
341
342    fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult<U256> {
343        self.0.storage_ref(address, index)
344    }
345
346    fn block_hash_ref(&self, number: u64) -> DatabaseResult<B256> {
347        self.0.block_hash_ref(number)
348    }
349}
350
351impl MaybeFullDatabase for StateDb {
352    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
353        self.0.maybe_as_full_db()
354    }
355
356    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
357        self.0.clear_into_state_snapshot()
358    }
359
360    fn read_as_state_snapshot(&self) -> StateSnapshot {
361        self.0.read_as_state_snapshot()
362    }
363
364    fn clear(&mut self) {
365        self.0.clear()
366    }
367
368    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
369        self.0.init_from_state_snapshot(state_snapshot)
370    }
371}
372
373/// Legacy block environment from before v1.3.
374#[derive(Debug, Deserialize)]
375#[serde(rename_all = "snake_case")]
376pub struct LegacyBlockEnv {
377    pub number: Option<StringOrU64>,
378    #[serde(alias = "coinbase")]
379    pub beneficiary: Option<Address>,
380    pub timestamp: Option<StringOrU64>,
381    pub gas_limit: Option<StringOrU64>,
382    pub basefee: Option<StringOrU64>,
383    pub difficulty: Option<StringOrU64>,
384    pub prevrandao: Option<B256>,
385    pub blob_excess_gas_and_price: Option<LegacyBlobExcessGasAndPrice>,
386}
387
388/// Legacy blob excess gas and price structure from before v1.3.
389#[derive(Debug, Deserialize)]
390pub struct LegacyBlobExcessGasAndPrice {
391    pub excess_blob_gas: u64,
392    pub blob_gasprice: u64,
393}
394
395/// Legacy string or u64 type from before v1.3.
396#[derive(Debug, Deserialize)]
397#[serde(untagged)]
398pub enum StringOrU64 {
399    Hex(String),
400    Dec(u64),
401}
402
403impl StringOrU64 {
404    pub fn to_u64(&self) -> Option<u64> {
405        match self {
406            Self::Dec(n) => Some(*n),
407            Self::Hex(s) => s.strip_prefix("0x").and_then(|s| u64::from_str_radix(s, 16).ok()),
408        }
409    }
410
411    pub fn to_u256(&self) -> Option<U256> {
412        match self {
413            Self::Dec(n) => Some(U256::from(*n)),
414            Self::Hex(s) => s.strip_prefix("0x").and_then(|s| U256::from_str_radix(s, 16).ok()),
415        }
416    }
417}
418
419/// Converts a `LegacyBlockEnv` to a `BlockEnv`, handling the conversion of legacy fields.
420impl TryFrom<LegacyBlockEnv> for BlockEnv {
421    type Error = &'static str;
422
423    fn try_from(legacy: LegacyBlockEnv) -> Result<Self, Self::Error> {
424        Ok(Self {
425            number: legacy.number.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
426            beneficiary: legacy.beneficiary.unwrap_or(Address::ZERO),
427            timestamp: legacy.timestamp.and_then(|v| v.to_u256()).unwrap_or(U256::ONE),
428            gas_limit: legacy.gas_limit.and_then(|v| v.to_u64()).unwrap_or(u64::MAX),
429            basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0),
430            difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
431            prevrandao: legacy.prevrandao.or(Some(B256::ZERO)),
432            blob_excess_gas_and_price: legacy
433                .blob_excess_gas_and_price
434                .map(|v| BlobExcessGasAndPrice::new(v.excess_blob_gas, v.blob_gasprice))
435                .or_else(|| {
436                    Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE))
437                }),
438        })
439    }
440}
441
442/// Custom deserializer for `BlockEnv` that handles both v1.2 and v1.3+ formats.
443fn deserialize_block_env_compat<'de, D>(deserializer: D) -> Result<Option<BlockEnv>, D::Error>
444where
445    D: Deserializer<'de>,
446{
447    let value: Option<Value> = Option::deserialize(deserializer)?;
448    let Some(value) = value else {
449        return Ok(None);
450    };
451
452    if let Ok(env) = BlockEnv::deserialize(&value) {
453        return Ok(Some(env));
454    }
455
456    let legacy: LegacyBlockEnv = serde_json::from_value(value).map_err(|e| {
457        D::Error::custom(format!("Legacy deserialization of `BlockEnv` failed: {e}"))
458    })?;
459
460    Ok(Some(BlockEnv::try_from(legacy).map_err(D::Error::custom)?))
461}
462
463/// Custom deserializer for `best_block_number` that handles both v1.2 and v1.3+ formats.
464fn deserialize_best_block_number_compat<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
465where
466    D: Deserializer<'de>,
467{
468    let value: Option<Value> = Option::deserialize(deserializer)?;
469    let Some(value) = value else {
470        return Ok(None);
471    };
472
473    let number = match value {
474        Value::Number(n) => n.as_u64(),
475        Value::String(s) => {
476            if let Some(s) = s.strip_prefix("0x") {
477                u64::from_str_radix(s, 16).ok()
478            } else {
479                s.parse().ok()
480            }
481        }
482        _ => None,
483    };
484
485    Ok(number)
486}
487
488#[derive(Clone, Debug, Default, Serialize, Deserialize)]
489pub struct SerializableState {
490    /// The block number of the state
491    ///
492    /// Note: This is an Option for backwards compatibility: <https://github.com/foundry-rs/foundry/issues/5460>
493    #[serde(deserialize_with = "deserialize_block_env_compat")]
494    pub block: Option<BlockEnv>,
495    pub accounts: BTreeMap<Address, SerializableAccountRecord>,
496    /// The best block number of the state, can be different from block number (Arbitrum chain).
497    #[serde(deserialize_with = "deserialize_best_block_number_compat")]
498    pub best_block_number: Option<u64>,
499    #[serde(default)]
500    pub blocks: Vec<SerializableBlock>,
501    #[serde(default)]
502    pub transactions: Vec<SerializableTransaction>,
503    /// Historical states of accounts and storage at particular block hashes.
504    ///
505    /// Note: This is an Option for backwards compatibility.
506    #[serde(default)]
507    pub historical_states: Option<SerializableHistoricalStates>,
508}
509
510impl SerializableState {
511    /// Loads the `Genesis` object from the given json file path
512    pub fn load(path: impl AsRef<Path>) -> Result<Self, FsPathError> {
513        let path = path.as_ref();
514        if path.is_dir() {
515            foundry_common::fs::read_json_file(&path.join("state.json"))
516        } else {
517            foundry_common::fs::read_json_file(path)
518        }
519    }
520
521    /// This is used as the clap `value_parser` implementation
522    #[cfg(feature = "cmd")]
523    pub(crate) fn parse(path: &str) -> Result<Self, String> {
524        Self::load(path).map_err(|err| err.to_string())
525    }
526}
527
528#[derive(Clone, Debug, Serialize, Deserialize)]
529pub struct SerializableAccountRecord {
530    pub nonce: u64,
531    pub balance: U256,
532    pub code: Bytes,
533
534    #[serde(deserialize_with = "deserialize_btree")]
535    pub storage: BTreeMap<B256, B256>,
536}
537
538fn deserialize_btree<'de, D>(deserializer: D) -> Result<BTreeMap<B256, B256>, D::Error>
539where
540    D: Deserializer<'de>,
541{
542    struct BTreeVisitor;
543
544    impl<'de> Visitor<'de> for BTreeVisitor {
545        type Value = BTreeMap<B256, B256>;
546
547        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
548            formatter.write_str("a mapping of hex encoded storage slots to hex encoded state data")
549        }
550
551        fn visit_map<M>(self, mut mapping: M) -> Result<BTreeMap<B256, B256>, M::Error>
552        where
553            M: MapAccess<'de>,
554        {
555            let mut btree = BTreeMap::new();
556            while let Some((key, value)) = mapping.next_entry::<U256, U256>()? {
557                btree.insert(B256::from(key), B256::from(value));
558            }
559
560            Ok(btree)
561        }
562    }
563
564    deserializer.deserialize_map(BTreeVisitor)
565}
566
567/// Defines a backwards-compatible enum for transactions.
568/// This is essential for maintaining compatibility with state dumps
569/// created before the changes introduced in PR #8411.
570///
571/// The enum can represent either a `TypedTransaction` or a `MaybeImpersonatedTransaction`,
572/// depending on the data being deserialized. This flexibility ensures that older state
573/// dumps can still be loaded correctly, even after the changes in #8411.
574#[derive(Clone, Debug, Serialize, Deserialize)]
575#[serde(untagged)]
576pub enum SerializableTransactionType {
577    TypedTransaction(FoundryTxEnvelope),
578    MaybeImpersonatedTransaction(MaybeImpersonatedTransaction),
579}
580
581#[derive(Clone, Debug, Serialize, Deserialize)]
582pub struct SerializableBlock {
583    pub header: Header,
584    pub transactions: Vec<SerializableTransactionType>,
585    pub ommers: Vec<Header>,
586}
587
588impl From<Block> for SerializableBlock {
589    fn from(block: Block) -> Self {
590        Self {
591            header: block.header,
592            transactions: block.body.transactions.into_iter().map(Into::into).collect(),
593            ommers: block.body.ommers.into_iter().collect(),
594        }
595    }
596}
597
598impl From<SerializableBlock> for Block {
599    fn from(block: SerializableBlock) -> Self {
600        let transactions = block.transactions.into_iter().map(Into::into).collect();
601        let ommers = block.ommers;
602        let body = BlockBody { transactions, ommers, withdrawals: None };
603        Self::new(block.header, body)
604    }
605}
606
607impl From<MaybeImpersonatedTransaction> for SerializableTransactionType {
608    fn from(transaction: MaybeImpersonatedTransaction) -> Self {
609        Self::MaybeImpersonatedTransaction(transaction)
610    }
611}
612
613impl From<SerializableTransactionType> for MaybeImpersonatedTransaction {
614    fn from(transaction: SerializableTransactionType) -> Self {
615        match transaction {
616            SerializableTransactionType::TypedTransaction(tx) => Self::new(tx),
617            SerializableTransactionType::MaybeImpersonatedTransaction(tx) => tx,
618        }
619    }
620}
621
622#[derive(Clone, Debug, Serialize, Deserialize)]
623pub struct SerializableTransaction {
624    pub info: TransactionInfo,
625    pub receipt: FoundryReceiptEnvelope,
626    pub block_hash: B256,
627    pub block_number: u64,
628}
629
630impl From<MinedTransaction> for SerializableTransaction {
631    fn from(transaction: MinedTransaction) -> Self {
632        Self {
633            info: transaction.info,
634            receipt: transaction.receipt,
635            block_hash: transaction.block_hash,
636            block_number: transaction.block_number,
637        }
638    }
639}
640
641impl From<SerializableTransaction> for MinedTransaction {
642    fn from(transaction: SerializableTransaction) -> Self {
643        Self {
644            info: transaction.info,
645            receipt: transaction.receipt,
646            block_hash: transaction.block_hash,
647            block_number: transaction.block_number,
648        }
649    }
650}
651
652#[derive(Clone, Debug, Serialize, Deserialize, Default)]
653pub struct SerializableHistoricalStates(Vec<(B256, StateSnapshot)>);
654
655impl SerializableHistoricalStates {
656    pub const fn new(states: Vec<(B256, StateSnapshot)>) -> Self {
657        Self(states)
658    }
659}
660
661impl IntoIterator for SerializableHistoricalStates {
662    type Item = (B256, StateSnapshot);
663    type IntoIter = std::vec::IntoIter<Self::Item>;
664
665    fn into_iter(self) -> Self::IntoIter {
666        self.0.into_iter()
667    }
668}
669
670#[cfg(test)]
671mod test {
672    use super::*;
673
674    #[test]
675    fn test_deser_block() {
676        let block = r#"{
677            "header": {
678                "parentHash": "0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929",
679                "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
680                "miner": "0x0000000000000000000000000000000000000000",
681                "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1",
682                "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988",
683                "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa",
684                "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
685                "difficulty": "0x0",
686                "number": "0x2",
687                "gasLimit": "0x1c9c380",
688                "gasUsed": "0x5208",
689                "timestamp": "0x66cdc823",
690                "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
691                "nonce": "0x0000000000000000",
692                "baseFeePerGas": "0x342a1c58",
693                "blobGasUsed": "0x0",
694                "excessBlobGas": "0x0",
695                "extraData": "0x"
696            },
697            "transactions": [
698                {
699                    "type": "0x2",
700                    "chainId": "0x7a69",
701                    "nonce": "0x0",
702                    "gas": "0x5209",
703                    "maxFeePerGas": "0x77359401",
704                    "maxPriorityFeePerGas": "0x1",
705                    "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
706                    "value": "0x0",
707                    "accessList": [],
708                    "input": "0x",
709                    "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
710                    "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
711                    "yParity": "0x0",
712                    "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
713                }
714            ],
715            "ommers": []
716        }
717        "#;
718
719        let _block: SerializableBlock = serde_json::from_str(block).unwrap();
720    }
721}