Skip to main content

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