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