Skip to main content

anvil/eth/backend/
db.rs

1//! Helper types for working with [revm]
2
3use crate::mem::storage::MinedTransaction;
4use alloy_consensus::BlockBody;
5use alloy_eips::{eip4895::Withdrawals, eip7928::BlockAccessList};
6use alloy_evm::block::BalIndexedDatabase;
7use alloy_network::Network;
8use alloy_primitives::{
9    Address, B256, Bytes, U256, keccak256,
10    map::{AddressMap, HashMap, U256Map},
11};
12use alloy_rpc_types::BlockId;
13use anvil_core::eth::{
14    block::Block,
15    transaction::{MaybeImpersonatedTransaction, TransactionInfo},
16};
17use foundry_common::errors::FsPathError;
18use foundry_evm::backend::{
19    BlockchainDb, DatabaseError, DatabaseResult, EmptyDBWrapper, MemDb, RevertStateSnapshotAction,
20    StateSnapshot, existing_account,
21};
22use foundry_primitives::{FoundryHeader, FoundryReceiptEnvelope, FoundryTxEnvelope};
23use revm::{
24    Database, DatabaseCommit,
25    bytecode::Bytecode,
26    context::BlockEnv,
27    context_interface::block::BlobExcessGasAndPrice,
28    database::{AccountState, CacheDB, DatabaseRef, DbAccount, bal::BalState},
29    primitives::{KECCAK_EMPTY, eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, hardfork::SpecId},
30    state::{AccountInfo, bal::BlockAccessIndex},
31};
32use serde::{
33    Deserialize, Deserializer, Serialize,
34    de::{Error as DeError, MapAccess, Visitor},
35};
36use serde_json::Value;
37use std::{
38    collections::BTreeMap,
39    fmt::{self, Debug},
40    fs::File,
41    io::BufReader,
42    path::Path,
43};
44
45/// Number of preceding block hashes available to the EVM's `BLOCKHASH` opcode.
46pub(crate) const BLOCKHASH_HISTORY: u64 = 256;
47
48/// Execution inputs needed to replay a locally stored Monad block faithfully.
49#[cfg(feature = "monad")]
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
51pub struct MonadBlockReplayProfile {
52    /// Chain ID active when the block was executed.
53    pub execution_chain_id: u64,
54    /// Monad hardfork active when the block was executed.
55    pub hardfork: foundry_evm::hardfork::MonadHardfork,
56}
57
58/// Inserts a block hash, discards entries outside the EVM-visible cache, and returns its head.
59pub(crate) fn cache_block_hash(block_hashes: &mut U256Map<B256>, number: U256, hash: B256) -> U256 {
60    let head = block_hashes.keys().copied().max().map_or(number, |head| head.max(number));
61    let min_number = head.saturating_sub(U256::from(BLOCKHASH_HISTORY));
62    block_hashes.retain(|cached, _| *cached >= min_number && *cached <= head);
63    if number >= min_number {
64        block_hashes.insert(number, hash);
65    }
66    head
67}
68
69/// Helper trait get access to the full state data of the database
70pub trait MaybeFullDatabase: crate::mem::DatabaseRef + Send + Sync {
71    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
72        None
73    }
74
75    fn maybe_as_full_db_mut(&mut self) -> Option<&mut AddressMap<DbAccount>> {
76        None
77    }
78
79    /// Returns an owned, recursively merged view of all available accounts.
80    fn maybe_full_db(&self) -> Option<AddressMap<DbAccount>> {
81        self.maybe_as_full_db().cloned()
82    }
83
84    /// Returns whether snapshots of this database use structural sharing.
85    fn is_persistent(&self) -> bool {
86        false
87    }
88
89    /// Clear the state and move it into a new `StateSnapshot`.
90    fn clear_into_state_snapshot(&mut self) -> StateSnapshot;
91
92    /// Read the state snapshot.
93    ///
94    /// This clones all the states and returns a new `StateSnapshot`.
95    fn read_as_state_snapshot(&self) -> StateSnapshot;
96
97    /// Clears the entire database
98    fn clear(&mut self);
99
100    /// Reverses `clear_into_snapshot` by initializing the db's state with the state snapshot.
101    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot);
102}
103
104/// A borrowed, type-erased state that overlays are built on, so that EVMs are monomorphized once.
105pub type StateRef<'a> = &'a dyn MaybeFullDatabase;
106
107impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T
108where
109    &'a T: DatabaseRef<Error = DatabaseError>,
110{
111    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
112        T::maybe_as_full_db(self)
113    }
114
115    fn maybe_full_db(&self) -> Option<AddressMap<DbAccount>> {
116        T::maybe_full_db(self)
117    }
118
119    fn is_persistent(&self) -> bool {
120        T::is_persistent(self)
121    }
122
123    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
124        unreachable!("never called for DatabaseRef")
125    }
126
127    fn read_as_state_snapshot(&self) -> StateSnapshot {
128        unreachable!("never called for DatabaseRef")
129    }
130
131    fn clear(&mut self) {}
132
133    fn init_from_state_snapshot(&mut self, _state_snapshot: StateSnapshot) {}
134}
135
136impl<T: MaybeFullDatabase + ?Sized> MaybeFullDatabase for Box<T>
137where
138    Self: DatabaseRef<Error = DatabaseError>,
139{
140    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
141        T::maybe_as_full_db(self)
142    }
143
144    fn maybe_as_full_db_mut(&mut self) -> Option<&mut AddressMap<DbAccount>> {
145        T::maybe_as_full_db_mut(self)
146    }
147
148    fn maybe_full_db(&self) -> Option<AddressMap<DbAccount>> {
149        T::maybe_full_db(self)
150    }
151
152    fn is_persistent(&self) -> bool {
153        T::is_persistent(self)
154    }
155
156    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
157        T::clear_into_state_snapshot(self)
158    }
159
160    fn read_as_state_snapshot(&self) -> StateSnapshot {
161        T::read_as_state_snapshot(self)
162    }
163
164    fn clear(&mut self) {
165        T::clear(self)
166    }
167
168    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
169        T::init_from_state_snapshot(self, state_snapshot)
170    }
171}
172
173/// Helper trait to reset the DB if it's forked
174pub trait MaybeForkedDatabase {
175    fn maybe_reset(&mut self, _urls: Vec<String>, block_number: BlockId) -> Result<(), String>;
176
177    fn maybe_flush_cache(&self) -> Result<(), String>;
178
179    fn maybe_inner(&self) -> Result<&BlockchainDb, String>;
180}
181
182/// `dyn Db` satisfies all `alloy_evm::Database` requirements via its supertraits, but the
183/// blanket impl has an implicit `Sized` bound. Provide an explicit impl.
184impl alloy_evm::Database for dyn Db {}
185
186/// A wrapper around [`CacheDB`] that executes transactions at `spec`.
187#[derive(Debug)]
188pub struct AnvilCacheDB<T>(pub CacheDB<T>, BalState, SpecId);
189
190impl<T: DatabaseRef<Error = DatabaseError>> AnvilCacheDB<T> {
191    pub fn new(inner: T, spec: SpecId) -> Self {
192        Self(CacheDB::new(inner), BalState::new(), spec)
193    }
194
195    /// Enables EIP-7928 block access list recording.
196    pub fn enable_bal_recording(&mut self) {
197        self.1 = BalState::new().with_bal_builder();
198    }
199
200    /// Takes the recorded EIP-7928 block access list, if recording was enabled.
201    pub fn take_block_access_list(&mut self) -> Option<BlockAccessList> {
202        self.1.take_built_alloy_bal()
203    }
204}
205
206impl<T: DatabaseRef<Error = DatabaseError>> std::ops::Deref for AnvilCacheDB<T> {
207    type Target = CacheDB<T>;
208    fn deref(&self) -> &Self::Target {
209        &self.0
210    }
211}
212
213impl<T: DatabaseRef<Error = DatabaseError>> std::ops::DerefMut for AnvilCacheDB<T> {
214    fn deref_mut(&mut self) -> &mut Self::Target {
215        &mut self.0
216    }
217}
218
219impl<T: DatabaseRef<Error = DatabaseError> + fmt::Debug> Database for AnvilCacheDB<T> {
220    type Error = DatabaseError;
221
222    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
223        Ok(existing_account(self.2, self.0.basic(address)?))
224    }
225
226    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
227        self.0.code_by_hash(code_hash)
228    }
229
230    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
231        self.0.storage(address, index)
232    }
233
234    fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
235        self.0.block_hash(number)
236    }
237}
238
239impl<T: DatabaseRef<Error = DatabaseError>> DatabaseRef for AnvilCacheDB<T> {
240    type Error = DatabaseError;
241
242    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
243        self.0.basic_ref(address)
244    }
245
246    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
247        self.0.code_by_hash_ref(code_hash)
248    }
249
250    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
251        self.0.storage_ref(address, index)
252    }
253
254    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
255        self.0.block_hash_ref(number)
256    }
257}
258
259impl<T: DatabaseRef<Error = DatabaseError> + fmt::Debug> DatabaseCommit for AnvilCacheDB<T> {
260    fn commit(&mut self, changes: revm::state::EvmState) {
261        self.1.commit(&changes);
262        self.0.commit(changes)
263    }
264}
265
266impl<T: DatabaseRef<Error = DatabaseError> + fmt::Debug> BalIndexedDatabase for AnvilCacheDB<T> {
267    fn set_bal_index(&mut self, index: u64) {
268        self.1.bal_index = BlockAccessIndex::new(index);
269    }
270
271    fn bump_bal_index(&mut self) {
272        self.1.bump_bal_index();
273    }
274}
275
276impl<T: DatabaseRef<Error = DatabaseError> + fmt::Debug> BalIndexedDatabase
277    for &mut AnvilCacheDB<T>
278{
279    fn set_bal_index(&mut self, index: u64) {
280        (**self).set_bal_index(index);
281    }
282
283    fn bump_bal_index(&mut self) {
284        (**self).bump_bal_index();
285    }
286}
287
288/// A read-only view of a database that reports empty accounts as absent.
289///
290/// Anvil's databases return a default [`AccountInfo`] for an account they don't hold (see
291/// [`EmptyDBWrapper`]), so an absent account can't be told apart from an empty one. Since
292/// EIP-161 an empty account can't be created and is deleted when touched, so this view lets a
293/// pre-state lookup treat both as absent. An account that already exists while empty, from genesis
294/// or before Spurious Dragon, is also reported as absent.
295#[derive(Debug)]
296pub(super) struct EmptyAsAbsentDb<T>(pub(super) T);
297
298impl<T: DatabaseRef> DatabaseRef for EmptyAsAbsentDb<T> {
299    type Error = T::Error;
300
301    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
302        Ok(self.0.basic_ref(address)?.filter(|info| !info.is_empty()))
303    }
304
305    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
306        self.0.code_by_hash_ref(code_hash)
307    }
308
309    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
310        self.0.storage_ref(address, index)
311    }
312
313    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
314        self.0.block_hash_ref(number)
315    }
316}
317
318/// This bundles all required revm traits
319pub trait Db:
320    Database<Error = DatabaseError>
321    + DatabaseCommit
322    + MaybeFullDatabase
323    + MaybeForkedDatabase
324    + Send
325    + Sync
326{
327    /// Inserts an account
328    fn insert_account(&mut self, address: Address, account: AccountInfo);
329
330    /// Sets the nonce of the given address
331    fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<()> {
332        let mut info = self.basic(address)?.unwrap_or_default();
333        info.nonce = nonce;
334        self.insert_account(address, info);
335        Ok(())
336    }
337
338    /// Sets the balance of the given address
339    fn set_balance(&mut self, address: Address, balance: U256) -> DatabaseResult<()> {
340        let mut info = self.basic(address)?.unwrap_or_default();
341        info.balance = balance;
342        self.insert_account(address, info);
343        Ok(())
344    }
345
346    /// Sets the code of the given address
347    fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> {
348        let mut info = self.basic(address)?.unwrap_or_default();
349        let code_hash = if code.as_ref().is_empty() {
350            KECCAK_EMPTY
351        } else {
352            B256::from_slice(&keccak256(code.as_ref())[..])
353        };
354        info.code_hash = code_hash;
355        info.code = Some(Bytecode::new_raw(code));
356        self.insert_account(address, info);
357        Ok(())
358    }
359
360    /// Sets the storage value at the given slot for the address
361    fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()>;
362
363    /// inserts a blockhash for the given number
364    fn insert_block_hash(&mut self, number: U256, hash: B256);
365
366    /// Replaces all cached block hashes.
367    fn set_block_hashes(&mut self, block_hashes: Vec<(U256, B256)>);
368
369    /// Write all chain data to serialized bytes buffer
370    fn dump_state(
371        &self,
372        at: BlockEnv,
373        best_number: u64,
374        blocks: Vec<SerializableBlock>,
375        transactions: Vec<SerializableTransaction>,
376        historical_states: Option<SerializableHistoricalStates>,
377    ) -> DatabaseResult<Option<SerializableState>>;
378
379    /// Deserialize and add all accounts to the backend storage.
380    fn load_state(&mut self, state: SerializableState) -> DatabaseResult<bool> {
381        for (addr, account) in state.accounts {
382            let old_account_nonce =
383                DatabaseRef::basic_ref(self, addr)?.map(|acc| acc.nonce).unwrap_or_default();
384            // use max nonce in case account is imported multiple times with difference
385            // nonces to prevent collisions
386            let nonce = std::cmp::max(old_account_nonce, account.nonce);
387
388            self.insert_account(
389                addr,
390                AccountInfo {
391                    balance: account.balance,
392                    code_hash: KECCAK_EMPTY, // will be set automatically
393                    code: if account.code.0.is_empty() {
394                        None
395                    } else {
396                        Some(Bytecode::new_raw(account.code))
397                    },
398                    nonce,
399                    account_id: None,
400                },
401            );
402
403            for (k, v) in account.storage {
404                self.set_storage_at(addr, k, v)?;
405            }
406        }
407        Ok(true)
408    }
409
410    /// Creates a new state snapshot.
411    fn snapshot_state(&mut self) -> U256;
412
413    /// Reverts a state snapshot.
414    ///
415    /// Returns `true` if the state snapshot was reverted.
416    fn revert_state(&mut self, state_snapshot: U256, action: RevertStateSnapshotAction) -> bool;
417
418    /// Deletes a state snapshot without reverting it.
419    fn delete_state_snapshot(&mut self, state_snapshot: U256) -> bool;
420
421    /// Returns the state root if possible to compute
422    fn maybe_state_root(&self) -> Option<B256> {
423        None
424    }
425
426    /// Returns the current, standalone state of the Db
427    fn current_state(&self) -> StateDb;
428
429    /// Rebuilds a historical state from a serialized state snapshot.
430    fn historical_state(&self, state_snapshot: StateSnapshot) -> StateDb {
431        let mut state = StateDb::new(MemDb::default());
432        state.init_from_state_snapshot(state_snapshot);
433        state
434    }
435}
436
437/// Convenience impl only used to use any `Db` on the fly as the db layer for revm's CacheDB
438/// This is useful to create blocks without actually writing to the `Db`, but rather in the cache of
439/// the `CacheDB` see also
440/// [Backend::pending_block()](crate::eth::backend::mem::Backend::pending_block())
441impl<T> Db for CacheDB<T>
442where
443    T: DatabaseRef<Error = DatabaseError> + MaybeFullDatabase + Send + Sync + Clone + fmt::Debug,
444{
445    fn insert_account(&mut self, address: Address, account: AccountInfo) {
446        self.insert_account_info(address, account)
447    }
448
449    fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> {
450        self.insert_account_storage(address, slot.into(), val.into())
451    }
452
453    fn insert_block_hash(&mut self, number: U256, hash: B256) {
454        cache_block_hash(&mut self.cache.block_hashes, number, hash);
455    }
456
457    fn set_block_hashes(&mut self, block_hashes: Vec<(U256, B256)>) {
458        self.cache.block_hashes = block_hashes.into_iter().collect();
459    }
460
461    fn dump_state(
462        &self,
463        _at: BlockEnv,
464        _best_number: u64,
465        _blocks: Vec<SerializableBlock>,
466        _transaction: Vec<SerializableTransaction>,
467        _historical_states: Option<SerializableHistoricalStates>,
468    ) -> DatabaseResult<Option<SerializableState>> {
469        Ok(None)
470    }
471
472    fn snapshot_state(&mut self) -> U256 {
473        U256::ZERO
474    }
475
476    fn revert_state(&mut self, _state_snapshot: U256, _action: RevertStateSnapshotAction) -> bool {
477        false
478    }
479
480    fn delete_state_snapshot(&mut self, _state_snapshot: U256) -> bool {
481        false
482    }
483
484    fn maybe_state_root(&self) -> Option<B256> {
485        self.maybe_full_db().map(|accounts| crate::mem::state::state_root(&accounts))
486    }
487
488    fn current_state(&self) -> StateDb {
489        StateDb::new(MemDb::default())
490    }
491}
492
493impl<T: MaybeFullDatabase> MaybeFullDatabase for CacheDB<T> {
494    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
495        Some(&self.cache.accounts)
496    }
497
498    fn maybe_full_db(&self) -> Option<AddressMap<DbAccount>> {
499        let mut accounts = self.db.maybe_full_db()?;
500        for (address, overlay) in &self.cache.accounts {
501            if overlay.account_state == AccountState::NotExisting {
502                accounts.remove(address);
503                continue;
504            }
505            if overlay.account_state.is_storage_cleared() {
506                accounts.insert(*address, overlay.clone());
507                continue;
508            }
509
510            let mut account = accounts.remove(address).unwrap_or_default();
511            account.info = overlay.info.clone();
512            account.account_state = overlay.account_state.clone();
513            account.storage.extend(overlay.storage.clone());
514            accounts.insert(*address, account);
515        }
516        Some(accounts)
517    }
518
519    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
520        let db_accounts = std::mem::take(&mut self.cache.accounts);
521        let mut accounts = HashMap::default();
522        let mut account_storage = HashMap::default();
523        let mut account_states = HashMap::default();
524
525        for (addr, mut acc) in db_accounts {
526            account_storage.insert(addr, std::mem::take(&mut acc.storage));
527            if matches!(acc.account_state, AccountState::NotExisting | AccountState::StorageCleared)
528            {
529                account_states.insert(addr, acc.account_state);
530            }
531            let mut info = acc.info;
532            info.code = self.cache.contracts.remove(&info.code_hash());
533            accounts.insert(addr, info);
534        }
535        let block_hashes = std::mem::take(&mut self.cache.block_hashes);
536        StateSnapshot {
537            accounts,
538            storage: account_storage,
539            block_hashes,
540            account_states: Some(account_states),
541        }
542    }
543
544    fn read_as_state_snapshot(&self) -> StateSnapshot {
545        let mut accounts = HashMap::default();
546        let mut account_storage = HashMap::default();
547        let mut account_states = HashMap::default();
548
549        for (addr, acc) in &self.cache.accounts {
550            account_storage.insert(*addr, acc.storage.clone());
551            if matches!(acc.account_state, AccountState::NotExisting | AccountState::StorageCleared)
552            {
553                account_states.insert(*addr, acc.account_state.clone());
554            }
555            let mut info = acc.info.clone();
556            info.code = self.cache.contracts.get(&info.code_hash()).cloned();
557            accounts.insert(*addr, info);
558        }
559
560        let block_hashes = self.cache.block_hashes.clone();
561        StateSnapshot {
562            accounts,
563            storage: account_storage,
564            block_hashes,
565            account_states: Some(account_states),
566        }
567    }
568
569    fn clear(&mut self) {
570        self.clear_into_state_snapshot();
571    }
572
573    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
574        let StateSnapshot { accounts, mut storage, block_hashes, account_states } = state_snapshot;
575        let mut account_states = account_states.unwrap_or_default();
576
577        for (addr, mut acc) in accounts {
578            if let Some(code) = acc.code.take() {
579                self.cache.contracts.insert(acc.code_hash(), code);
580            }
581            self.cache.accounts.insert(
582                addr,
583                DbAccount {
584                    info: acc,
585                    account_state: account_states.remove(&addr).unwrap_or_default(),
586                    storage: storage.remove(&addr).unwrap_or_default(),
587                },
588            );
589        }
590        self.cache.block_hashes = block_hashes;
591    }
592}
593
594impl MaybeFullDatabase for EmptyDBWrapper {
595    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
596        StateSnapshot::default()
597    }
598
599    fn read_as_state_snapshot(&self) -> StateSnapshot {
600        StateSnapshot::default()
601    }
602
603    fn clear(&mut self) {}
604
605    fn init_from_state_snapshot(&mut self, _state_snapshot: StateSnapshot) {}
606}
607
608impl<T: DatabaseRef<Error = DatabaseError>> MaybeForkedDatabase for CacheDB<T> {
609    fn maybe_reset(&mut self, _urls: Vec<String>, _block_number: BlockId) -> Result<(), String> {
610        Err("not supported".to_string())
611    }
612
613    fn maybe_flush_cache(&self) -> Result<(), String> {
614        Err("not supported".to_string())
615    }
616
617    fn maybe_inner(&self) -> Result<&BlockchainDb, String> {
618        Err("not supported".to_string())
619    }
620}
621
622/// Represents a state at certain point
623#[derive(Debug)]
624pub struct StateDb(pub(crate) Box<dyn MaybeFullDatabase>);
625
626impl StateDb {
627    pub fn new(db: impl MaybeFullDatabase + 'static) -> Self {
628        Self(Box::new(db))
629    }
630
631    pub fn serialize_state(&mut self) -> StateSnapshot {
632        // Using read_as_snapshot makes sures we don't clear the historical state from the current
633        // instance.
634        self.read_as_state_snapshot()
635    }
636}
637
638impl DatabaseRef for StateDb {
639    type Error = DatabaseError;
640    fn basic_ref(&self, address: Address) -> DatabaseResult<Option<AccountInfo>> {
641        self.0.basic_ref(address)
642    }
643
644    fn code_by_hash_ref(&self, code_hash: B256) -> DatabaseResult<Bytecode> {
645        self.0.code_by_hash_ref(code_hash)
646    }
647
648    fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult<U256> {
649        self.0.storage_ref(address, index)
650    }
651
652    fn block_hash_ref(&self, number: u64) -> DatabaseResult<B256> {
653        self.0.block_hash_ref(number)
654    }
655}
656
657impl MaybeFullDatabase for StateDb {
658    fn maybe_as_full_db(&self) -> Option<&AddressMap<DbAccount>> {
659        self.0.maybe_as_full_db()
660    }
661
662    fn maybe_full_db(&self) -> Option<AddressMap<DbAccount>> {
663        self.0.maybe_full_db()
664    }
665
666    fn is_persistent(&self) -> bool {
667        self.0.is_persistent()
668    }
669
670    fn clear_into_state_snapshot(&mut self) -> StateSnapshot {
671        self.0.clear_into_state_snapshot()
672    }
673
674    fn read_as_state_snapshot(&self) -> StateSnapshot {
675        self.0.read_as_state_snapshot()
676    }
677
678    fn clear(&mut self) {
679        self.0.clear()
680    }
681
682    fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) {
683        self.0.init_from_state_snapshot(state_snapshot)
684    }
685}
686
687/// Legacy block environment from before v1.3.
688#[derive(Debug, Deserialize)]
689#[serde(rename_all = "snake_case")]
690pub struct LegacyBlockEnv {
691    pub number: Option<StringOrU64>,
692    #[serde(alias = "coinbase")]
693    pub beneficiary: Option<Address>,
694    pub timestamp: Option<StringOrU64>,
695    pub gas_limit: Option<StringOrU64>,
696    pub basefee: Option<StringOrU64>,
697    pub difficulty: Option<StringOrU64>,
698    pub prevrandao: Option<B256>,
699    pub blob_excess_gas_and_price: Option<LegacyBlobExcessGasAndPrice>,
700}
701
702/// Legacy blob excess gas and price structure from before v1.3.
703#[derive(Debug, Deserialize)]
704pub struct LegacyBlobExcessGasAndPrice {
705    pub excess_blob_gas: u64,
706    pub blob_gasprice: u128,
707}
708
709/// Legacy string or u64 type from before v1.3.
710#[derive(Debug, Deserialize)]
711#[serde(untagged)]
712pub enum StringOrU64 {
713    Hex(String),
714    Dec(u64),
715}
716
717impl StringOrU64 {
718    pub fn to_u64(&self) -> Option<u64> {
719        match self {
720            Self::Dec(n) => Some(*n),
721            Self::Hex(s) => s.strip_prefix("0x").and_then(|s| u64::from_str_radix(s, 16).ok()),
722        }
723    }
724
725    pub fn to_u256(&self) -> Option<U256> {
726        match self {
727            Self::Dec(n) => Some(U256::from(*n)),
728            Self::Hex(s) => s.strip_prefix("0x").and_then(|s| U256::from_str_radix(s, 16).ok()),
729        }
730    }
731}
732
733/// Converts a `LegacyBlockEnv` to a `BlockEnv`, handling the conversion of legacy fields.
734impl TryFrom<LegacyBlockEnv> for BlockEnv {
735    type Error = &'static str;
736
737    fn try_from(legacy: LegacyBlockEnv) -> Result<Self, Self::Error> {
738        Ok(Self {
739            number: legacy.number.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
740            beneficiary: legacy.beneficiary.unwrap_or(Address::ZERO),
741            timestamp: legacy.timestamp.and_then(|v| v.to_u256()).unwrap_or(U256::ONE),
742            gas_limit: legacy.gas_limit.and_then(|v| v.to_u64()).unwrap_or(u64::MAX),
743            basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0),
744            difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
745            prevrandao: legacy.prevrandao.or(Some(B256::ZERO)),
746            slot_num: 0,
747            blob_excess_gas_and_price: legacy
748                .blob_excess_gas_and_price
749                .map(|v| BlobExcessGasAndPrice {
750                    excess_blob_gas: v.excess_blob_gas,
751                    blob_gasprice: v.blob_gasprice,
752                })
753                .or_else(|| {
754                    Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE))
755                }),
756        })
757    }
758}
759
760/// Custom deserializer for `BlockEnv` that handles both v1.2 and v1.3+ formats.
761fn deserialize_block_env_compat<'de, D>(deserializer: D) -> Result<Option<BlockEnv>, D::Error>
762where
763    D: Deserializer<'de>,
764{
765    let value: Option<Value> = Option::deserialize(deserializer)?;
766    let Some(value) = value else {
767        return Ok(None);
768    };
769
770    if let Ok(env) = BlockEnv::deserialize(&value) {
771        return Ok(Some(env));
772    }
773
774    let legacy: LegacyBlockEnv = serde_json::from_value(value).map_err(|e| {
775        D::Error::custom(format!("Legacy deserialization of `BlockEnv` failed: {e}"))
776    })?;
777
778    Ok(Some(BlockEnv::try_from(legacy).map_err(D::Error::custom)?))
779}
780
781/// Custom deserializer for `best_block_number` that handles both v1.2 and v1.3+ formats.
782fn deserialize_best_block_number_compat<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
783where
784    D: Deserializer<'de>,
785{
786    let value: Option<Value> = Option::deserialize(deserializer)?;
787    let Some(value) = value else {
788        return Ok(None);
789    };
790
791    let number = match value {
792        Value::Number(n) => n.as_u64(),
793        Value::String(s) => {
794            if let Some(s) = s.strip_prefix("0x") {
795                u64::from_str_radix(s, 16).ok()
796            } else {
797                s.parse().ok()
798            }
799        }
800        _ => None,
801    };
802
803    Ok(number)
804}
805
806#[derive(Clone, Debug, Default, Serialize, Deserialize)]
807pub struct SerializableState {
808    /// The block number of the state
809    ///
810    /// Note: This is an Option for backwards compatibility: <https://github.com/foundry-rs/foundry/issues/5460>
811    #[serde(deserialize_with = "deserialize_block_env_compat")]
812    pub block: Option<BlockEnv>,
813    pub accounts: BTreeMap<Address, SerializableAccountRecord>,
814    /// The best block number of the state, can be different from block number (Arbitrum chain).
815    #[serde(deserialize_with = "deserialize_best_block_number_compat")]
816    pub best_block_number: Option<u64>,
817    #[serde(default)]
818    pub blocks: Vec<SerializableBlock>,
819    #[serde(default)]
820    pub transactions: Vec<SerializableTransaction>,
821    /// Authoritative Monad senders and EIP-7702 authorities for locally stored blocks.
822    ///
823    /// This metadata can differ from transaction-body recovery when signature impersonation was
824    /// used, so it is preserved even while the corresponding transaction bodies are retained.
825    #[cfg(feature = "monad")]
826    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
827    pub monad_block_participants: BTreeMap<B256, std::collections::BTreeSet<Address>>,
828    /// Execution profile used for each locally stored Monad block.
829    #[cfg(feature = "monad")]
830    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
831    pub monad_block_replay_profiles: BTreeMap<B256, MonadBlockReplayProfile>,
832    /// Historical states of accounts and storage at particular block hashes.
833    ///
834    /// Note: This is an Option for backwards compatibility.
835    #[serde(default)]
836    pub historical_states: Option<SerializableHistoricalStates>,
837}
838
839impl SerializableState {
840    /// Loads the `Genesis` object from the given json file path
841    pub fn load(path: impl AsRef<Path>) -> Result<Self, FsPathError> {
842        let mut path = path.as_ref().to_path_buf();
843        if path.is_dir() {
844            path = path.join("state.json");
845        }
846
847        let file = File::open(&path).map_err(|err| FsPathError::read(err, &path))?;
848        serde_json::from_reader(BufReader::new(file)).map_err(|err| {
849            if err.is_io() {
850                FsPathError::read(err.into(), &path)
851            } else {
852                FsPathError::ReadJson { source: err, path }
853            }
854        })
855    }
856
857    /// This is used as the clap `value_parser` implementation
858    #[cfg(feature = "cmd")]
859    pub(crate) fn parse(path: &str) -> Result<Self, String> {
860        Self::load(path).map_err(|err| err.to_string())
861    }
862}
863
864#[derive(Clone, Debug, Serialize, Deserialize)]
865pub struct SerializableAccountRecord {
866    pub nonce: u64,
867    pub balance: U256,
868    pub code: Bytes,
869
870    #[serde(deserialize_with = "deserialize_btree")]
871    pub storage: BTreeMap<B256, B256>,
872}
873
874fn deserialize_btree<'de, D>(deserializer: D) -> Result<BTreeMap<B256, B256>, D::Error>
875where
876    D: Deserializer<'de>,
877{
878    struct BTreeVisitor;
879
880    impl<'de> Visitor<'de> for BTreeVisitor {
881        type Value = BTreeMap<B256, B256>;
882
883        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
884            formatter.write_str("a mapping of hex encoded storage slots to hex encoded state data")
885        }
886
887        fn visit_map<M>(self, mut mapping: M) -> Result<BTreeMap<B256, B256>, M::Error>
888        where
889            M: MapAccess<'de>,
890        {
891            let mut btree = BTreeMap::new();
892            while let Some((key, value)) = mapping.next_entry::<U256, U256>()? {
893                btree.insert(B256::from(key), B256::from(value));
894            }
895
896            Ok(btree)
897        }
898    }
899
900    deserializer.deserialize_map(BTreeVisitor)
901}
902
903/// Defines a backwards-compatible enum for transactions.
904/// This is essential for maintaining compatibility with state dumps
905/// created before the changes introduced in PR #8411.
906///
907/// The enum can represent either a `TypedTransaction` or a `MaybeImpersonatedTransaction`,
908/// depending on the data being deserialized. This flexibility ensures that older state
909/// dumps can still be loaded correctly, even after the changes in #8411.
910#[derive(Clone, Debug, Serialize, Deserialize)]
911#[serde(untagged)]
912pub enum SerializableTransactionType {
913    TypedTransaction(FoundryTxEnvelope),
914    MaybeImpersonatedTransaction(MaybeImpersonatedTransaction<FoundryTxEnvelope>),
915}
916
917#[derive(Clone, Debug, Serialize, Deserialize)]
918pub struct SerializableBlock {
919    pub header: FoundryHeader,
920    pub transactions: Vec<SerializableTransactionType>,
921    pub ommers: Vec<FoundryHeader>,
922    #[serde(default)]
923    pub withdrawals: Option<Withdrawals>,
924}
925
926impl From<Block> for SerializableBlock {
927    fn from(block: Block) -> Self {
928        Self {
929            header: block.header,
930            transactions: block.body.transactions.into_iter().map(Into::into).collect(),
931            ommers: block.body.ommers.into_iter().collect(),
932            withdrawals: block.body.withdrawals,
933        }
934    }
935}
936
937impl From<SerializableBlock> for Block {
938    fn from(block: SerializableBlock) -> Self {
939        let transactions = block.transactions.into_iter().map(Into::into).collect();
940        let ommers = block.ommers;
941        let body = BlockBody { transactions, ommers, withdrawals: block.withdrawals };
942        Self::new(block.header, body)
943    }
944}
945
946impl From<MaybeImpersonatedTransaction<FoundryTxEnvelope>> for SerializableTransactionType {
947    fn from(transaction: MaybeImpersonatedTransaction<FoundryTxEnvelope>) -> Self {
948        Self::MaybeImpersonatedTransaction(transaction)
949    }
950}
951
952impl From<SerializableTransactionType> for MaybeImpersonatedTransaction<FoundryTxEnvelope> {
953    fn from(transaction: SerializableTransactionType) -> Self {
954        match transaction {
955            SerializableTransactionType::TypedTransaction(tx) => Self::new(tx),
956            SerializableTransactionType::MaybeImpersonatedTransaction(tx) => tx,
957        }
958    }
959}
960
961#[derive(Clone, Debug, Serialize, Deserialize)]
962pub struct SerializableTransaction {
963    pub info: TransactionInfo,
964    pub receipt: FoundryReceiptEnvelope,
965    pub block_hash: B256,
966    pub block_number: u64,
967}
968
969impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> From<MinedTransaction<N>>
970    for SerializableTransaction
971{
972    fn from(transaction: MinedTransaction<N>) -> Self {
973        Self {
974            info: transaction.info,
975            receipt: transaction.receipt,
976            block_hash: transaction.block_hash,
977            block_number: transaction.block_number,
978        }
979    }
980}
981
982impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> From<SerializableTransaction>
983    for MinedTransaction<N>
984{
985    fn from(transaction: SerializableTransaction) -> Self {
986        Self {
987            info: transaction.info,
988            receipt: transaction.receipt,
989            block_hash: transaction.block_hash,
990            block_number: transaction.block_number,
991        }
992    }
993}
994
995#[derive(Clone, Debug, Serialize, Deserialize, Default)]
996pub struct SerializableHistoricalStates(Vec<(B256, StateSnapshot)>);
997
998impl SerializableHistoricalStates {
999    pub const fn new(states: Vec<(B256, StateSnapshot)>) -> Self {
1000        Self(states)
1001    }
1002}
1003
1004impl IntoIterator for SerializableHistoricalStates {
1005    type Item = (B256, StateSnapshot);
1006    type IntoIter = std::vec::IntoIter<Self::Item>;
1007
1008    fn into_iter(self) -> Self::IntoIter {
1009        self.0.into_iter()
1010    }
1011}
1012
1013#[cfg(test)]
1014mod test {
1015    use super::*;
1016    use crate::eth::backend::mem::fork_db::ForkedDatabase;
1017    use alloy_consensus::Header;
1018    use alloy_network::AnyNetwork;
1019    use alloy_provider::ProviderBuilder;
1020    use alloy_transport::mock::Asserter;
1021    use foundry_evm::backend::{BlockchainDbMeta, SharedBackend};
1022    use std::fs;
1023
1024    #[test]
1025    fn loads_state_from_file_or_directory() {
1026        let tmp = tempfile::tempdir().unwrap();
1027        let state_path = tmp.path().join("state.json");
1028        fs::write(&state_path, serde_json::to_vec(&SerializableState::default()).unwrap()).unwrap();
1029
1030        assert!(SerializableState::load(&state_path).unwrap().accounts.is_empty());
1031        assert!(SerializableState::load(tmp.path()).unwrap().accounts.is_empty());
1032
1033        fs::write(&state_path, b"not json").unwrap();
1034        let Err(FsPathError::ReadJson { path, .. }) = SerializableState::load(tmp.path()) else {
1035            panic!("expected invalid JSON error")
1036        };
1037        assert_eq!(path, state_path);
1038
1039        let missing_path = tmp.path().join("missing.json");
1040        let Err(FsPathError::Read { path, .. }) = SerializableState::load(&missing_path) else {
1041            panic!("expected file read error")
1042        };
1043        assert_eq!(path, missing_path);
1044    }
1045
1046    #[test]
1047    fn cache_db_full_state_merges_base_and_overlay() {
1048        let preserved = Address::with_last_byte(1);
1049        let updated = Address::with_last_byte(2);
1050        let deleted = Address::with_last_byte(3);
1051        let cleared = Address::with_last_byte(4);
1052        let deleted_slot = U256::ONE;
1053        let updated_slot = U256::from(2);
1054
1055        let mut base = MemDb::default();
1056        base.insert_account(preserved, AccountInfo::from_balance(U256::ONE));
1057        base.insert_account(updated, AccountInfo::from_balance(U256::from(2)));
1058        base.set_storage_at(updated, deleted_slot.into(), B256::with_last_byte(10)).unwrap();
1059        base.set_storage_at(updated, updated_slot.into(), B256::with_last_byte(11)).unwrap();
1060        base.insert_account(deleted, AccountInfo::from_balance(U256::from(3)));
1061        base.insert_account(cleared, AccountInfo::from_balance(U256::from(4)));
1062        base.set_storage_at(cleared, deleted_slot.into(), B256::with_last_byte(11)).unwrap();
1063
1064        let mut cache = CacheDB::new(base);
1065        cache.insert_account_info(updated, AccountInfo::from_balance(U256::from(20)));
1066        cache.insert_account_storage(updated, deleted_slot, U256::ZERO).unwrap();
1067        cache.insert_account_storage(updated, updated_slot, U256::from(12)).unwrap();
1068        cache.cache.accounts.insert(deleted, DbAccount::new_not_existing());
1069        cache.cache.accounts.insert(
1070            cleared,
1071            DbAccount {
1072                info: AccountInfo::from_balance(U256::from(40)),
1073                account_state: AccountState::StorageCleared,
1074                ..Default::default()
1075            },
1076        );
1077
1078        let accounts = cache.maybe_full_db().unwrap();
1079        assert_eq!(accounts[&preserved].info.balance, U256::ONE);
1080        assert_eq!(accounts[&updated].info.balance, U256::from(20));
1081        assert_eq!(accounts[&updated].storage[&deleted_slot], U256::ZERO);
1082        assert_eq!(accounts[&updated].storage[&updated_slot], U256::from(12));
1083        assert!(!accounts.contains_key(&deleted));
1084        assert!(accounts[&cleared].storage.is_empty());
1085        let mut expected = accounts;
1086        expected.get_mut(&updated).unwrap().storage.remove(&deleted_slot);
1087        assert_eq!(
1088            cache.maybe_full_db().map(|accounts| crate::mem::state::state_root(&accounts)),
1089            Some(crate::mem::state::state_root(&expected))
1090        );
1091    }
1092
1093    #[test]
1094    fn cache_db_state_snapshot_preserves_account_states() {
1095        let touched = Address::with_last_byte(1);
1096        let deleted = Address::with_last_byte(2);
1097        let cleared = Address::with_last_byte(3);
1098        let (kept_slot, missing_slot) = (U256::ONE, U256::from(2));
1099        // The base stands in for a fork whose storage must not leak into cleared accounts.
1100        let base = || {
1101            let mut base = MemDb::default();
1102            for address in [touched, deleted, cleared] {
1103                base.insert_account(address, AccountInfo::from_balance(U256::ONE));
1104                base.set_storage_at(address, missing_slot.into(), B256::with_last_byte(9)).unwrap();
1105            }
1106            base
1107        };
1108
1109        let mut cache = CacheDB::new(base());
1110        cache.insert_account_storage(touched, kept_slot, U256::from(5)).unwrap();
1111        cache.cache.accounts.insert(deleted, DbAccount::new_not_existing());
1112        let mut cleared_account = DbAccount {
1113            info: AccountInfo::from_balance(U256::from(40)),
1114            account_state: AccountState::StorageCleared,
1115            ..Default::default()
1116        };
1117        cleared_account.storage.insert(kept_slot, U256::from(6));
1118        cache.cache.accounts.insert(cleared, cleared_account);
1119
1120        let snapshot = cache.read_as_state_snapshot();
1121        let mut restored = CacheDB::new(base());
1122        restored.init_from_state_snapshot(
1123            serde_json::from_str(&serde_json::to_string(&snapshot).unwrap()).unwrap(),
1124        );
1125
1126        assert_eq!(restored.storage_ref(touched, kept_slot).unwrap(), U256::from(5));
1127        assert_eq!(restored.storage_ref(touched, missing_slot).unwrap(), U256::from(9));
1128        assert_eq!(restored.basic_ref(deleted).unwrap(), None);
1129        assert_eq!(restored.storage_ref(deleted, missing_slot).unwrap(), U256::ZERO);
1130        assert_eq!(restored.basic_ref(cleared).unwrap().unwrap().balance, U256::from(40));
1131        assert_eq!(restored.storage_ref(cleared, kept_slot).unwrap(), U256::from(6));
1132        assert_eq!(restored.storage_ref(cleared, missing_slot).unwrap(), U256::ZERO);
1133        assert_eq!(restored.clear_into_state_snapshot().account_states, snapshot.account_states);
1134    }
1135
1136    #[test]
1137    fn test_deser_block() {
1138        let block = r#"{
1139            "header": {
1140                "parentHash": "0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929",
1141                "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1142                "miner": "0x0000000000000000000000000000000000000000",
1143                "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1",
1144                "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988",
1145                "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa",
1146                "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
1147                "difficulty": "0x0",
1148                "number": "0x2",
1149                "gasLimit": "0x1c9c380",
1150                "gasUsed": "0x5208",
1151                "timestamp": "0x66cdc823",
1152                "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1153                "nonce": "0x0000000000000000",
1154                "baseFeePerGas": "0x342a1c58",
1155                "blobGasUsed": "0x0",
1156                "excessBlobGas": "0x0",
1157                "extraData": "0x"
1158            },
1159            "transactions": [
1160                {
1161                    "type": "0x2",
1162                    "chainId": "0x7a69",
1163                    "nonce": "0x0",
1164                    "gas": "0x5209",
1165                    "maxFeePerGas": "0x77359401",
1166                    "maxPriorityFeePerGas": "0x1",
1167                    "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1168                    "value": "0x0",
1169                    "accessList": [],
1170                    "input": "0x",
1171                    "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0",
1172                    "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd",
1173                    "yParity": "0x0",
1174                    "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"
1175                }
1176            ],
1177            "ommers": []
1178        }
1179        "#;
1180
1181        let _block: SerializableBlock = serde_json::from_str(block).unwrap();
1182    }
1183
1184    #[test]
1185    fn test_block_withdrawals_preserved() {
1186        use alloy_eips::eip4895::Withdrawal;
1187
1188        // create a block with withdrawals (like post-Shanghai blocks)
1189        let withdrawal = Withdrawal {
1190            index: 42,
1191            validator_index: 123,
1192            address: Address::repeat_byte(1),
1193            amount: 1000,
1194        };
1195
1196        let header = Header::default();
1197        let body = BlockBody {
1198            transactions: vec![],
1199            ommers: vec![],
1200            withdrawals: Some(vec![withdrawal].into()),
1201        };
1202        let block = Block::new(header.into(), body);
1203
1204        // convert to SerializableBlock and back
1205        let serializable = SerializableBlock::from(block);
1206        let restored = Block::from(serializable);
1207
1208        // withdrawals should be preserved
1209        assert!(restored.body.withdrawals.is_some());
1210        let withdrawals = restored.body.withdrawals.unwrap();
1211        assert_eq!(withdrawals.len(), 1);
1212        assert_eq!(withdrawals[0].index, 42);
1213        assert_eq!(withdrawals[0].validator_index, 123);
1214    }
1215
1216    #[test]
1217    fn load_state_fork_read_error_allows_snapshot_rollback() {
1218        let provider =
1219            ProviderBuilder::<_, _, AnyNetwork>::default().connect_mocked_client(Asserter::new());
1220        let remote = BlockchainDb::new(
1221            BlockchainDbMeta::new(BlockEnv::default(), "http://localhost".to_string()),
1222            None,
1223        );
1224        let (backend, handler) = SharedBackend::new(provider, remote.clone(), None);
1225        drop(handler);
1226        let mut db = ForkedDatabase::new(backend, remote);
1227        let cached = Address::with_last_byte(1);
1228        let uncached = Address::with_last_byte(2);
1229        let original = AccountInfo { nonce: 7, balance: U256::ONE, ..Default::default() };
1230        db.insert_account(cached, original.clone());
1231        let snapshot = db.snapshot_state();
1232        let mut state = SerializableState::default();
1233        for address in [cached, uncached] {
1234            state.accounts.insert(
1235                address,
1236                SerializableAccountRecord {
1237                    nonce: 3,
1238                    balance: U256::from(10),
1239                    code: Bytes::new(),
1240                    storage: Default::default(),
1241                },
1242            );
1243        }
1244
1245        // The cached account imports first; the next lookup reaches the closed fork backend.
1246        assert!(matches!(db.load_state(state), Err(DatabaseError::Send(_))));
1247        let imported = db.basic_ref(cached).unwrap().unwrap();
1248        assert_eq!(imported.nonce, original.nonce);
1249        assert_eq!(imported.balance, U256::from(10));
1250        assert!(!db.database().cache.accounts.contains_key(&uncached));
1251
1252        // Mirror Backend::load_state's error path and restore the earlier partial import.
1253        assert!(db.revert_state(snapshot, RevertStateSnapshotAction::RevertRemove));
1254        assert_eq!(db.basic_ref(cached).unwrap().unwrap(), original);
1255        assert!(!db.database().cache.accounts.contains_key(&uncached));
1256    }
1257}