foundry_evm_core/backend/
in_memory_db.rs

1//! In-memory database.
2
3use crate::state_snapshot::StateSnapshots;
4use alloy_primitives::{Address, B256, U256};
5use foundry_fork_db::DatabaseError;
6use revm::{
7    bytecode::Bytecode,
8    database::{CacheDB, DatabaseRef, EmptyDB},
9    primitives::HashMap as Map,
10    state::{Account, AccountInfo},
11    Database, DatabaseCommit,
12};
13
14/// Type alias for an in-memory database.
15///
16/// See [`EmptyDBWrapper`].
17pub type FoundryEvmInMemoryDB = CacheDB<EmptyDBWrapper>;
18
19/// In-memory [`Database`] for Anvil.
20///
21/// This acts like a wrapper type for [`FoundryEvmInMemoryDB`] but is capable of applying snapshots.
22#[derive(Debug)]
23pub struct MemDb {
24    pub inner: FoundryEvmInMemoryDB,
25    pub state_snapshots: StateSnapshots<FoundryEvmInMemoryDB>,
26}
27
28impl Default for MemDb {
29    fn default() -> Self {
30        Self { inner: CacheDB::new(Default::default()), state_snapshots: Default::default() }
31    }
32}
33
34impl DatabaseRef for MemDb {
35    type Error = DatabaseError;
36
37    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
38        DatabaseRef::basic_ref(&self.inner, address)
39    }
40
41    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
42        DatabaseRef::code_by_hash_ref(&self.inner, code_hash)
43    }
44
45    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
46        DatabaseRef::storage_ref(&self.inner, address, index)
47    }
48
49    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
50        DatabaseRef::block_hash_ref(&self.inner, number)
51    }
52}
53
54impl Database for MemDb {
55    type Error = DatabaseError;
56
57    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
58        // Note: this will always return `Some(AccountInfo)`, See `EmptyDBWrapper`
59        Database::basic(&mut self.inner, address)
60    }
61
62    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
63        Database::code_by_hash(&mut self.inner, code_hash)
64    }
65
66    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
67        Database::storage(&mut self.inner, address, index)
68    }
69
70    fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
71        Database::block_hash(&mut self.inner, number)
72    }
73}
74
75impl DatabaseCommit for MemDb {
76    fn commit(&mut self, changes: Map<Address, Account>) {
77        DatabaseCommit::commit(&mut self.inner, changes)
78    }
79}
80
81/// An empty database that always returns default values when queried.
82///
83/// This is just a simple wrapper for `revm::EmptyDB` but implements `DatabaseError` instead, this
84/// way we can unify all different `Database` impls
85///
86/// This will also _always_ return `Some(AccountInfo)`:
87///
88/// The [`Database`] implementation for `CacheDB` manages an `AccountState` for the
89/// `DbAccount`, this will be set to `AccountState::NotExisting` if the account does not exist yet.
90/// This is because there's a distinction between "non-existing" and "empty",
91/// see <https://github.com/bluealloy/revm/blob/8f4348dc93022cffb3730d9db5d3ab1aad77676a/crates/revm/src/db/in_memory_db.rs#L81-L83>.
92/// If an account is `NotExisting`, `Database::basic_ref` will always return `None` for the
93/// requested `AccountInfo`.
94///
95/// To prevent this, we ensure that a missing account is never marked as `NotExisting` by always
96/// returning `Some` with this type, which will then insert a default [`AccountInfo`] instead
97/// of one marked as `AccountState::NotExisting`.
98#[derive(Clone, Debug, Default)]
99pub struct EmptyDBWrapper(EmptyDB);
100
101impl DatabaseRef for EmptyDBWrapper {
102    type Error = DatabaseError;
103
104    fn basic_ref(&self, _address: Address) -> Result<Option<AccountInfo>, Self::Error> {
105        // Note: this will always return `Some(AccountInfo)`, for the reason explained above
106        Ok(Some(AccountInfo::default()))
107    }
108
109    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
110        Ok(self.0.code_by_hash_ref(code_hash)?)
111    }
112    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
113        Ok(self.0.storage_ref(address, index)?)
114    }
115
116    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
117        Ok(self.0.block_hash_ref(number)?)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use alloy_primitives::b256;
125
126    /// Ensures the `Database(Ref)` implementation for `revm::CacheDB` works as expected
127    ///
128    /// Demonstrates how calling `Database::basic` works if an account does not exist
129    #[test]
130    fn cache_db_insert_basic_non_existing() {
131        let mut db = CacheDB::new(EmptyDB::default());
132        let address = Address::random();
133        // call `basic` on a non-existing account
134        let info = Database::basic(&mut db, address).unwrap();
135        assert!(info.is_none());
136        let mut info = info.unwrap_or_default();
137        info.balance = U256::from(500u64);
138
139        // insert the modified account info
140        db.insert_account_info(address, info);
141
142        // when fetching again, the `AccountInfo` is still `None` because the state of the account
143        // is `AccountState::NotExisting`, see <https://github.com/bluealloy/revm/blob/8f4348dc93022cffb3730d9db5d3ab1aad77676a/crates/revm/src/db/in_memory_db.rs#L217-L226>
144        let info = Database::basic(&mut db, address).unwrap();
145        assert!(info.is_none());
146    }
147
148    /// Demonstrates how to insert a new account but not mark it as non-existing
149    #[test]
150    fn cache_db_insert_basic_default() {
151        let mut db = CacheDB::new(EmptyDB::default());
152        let address = Address::random();
153
154        // We use `basic_ref` here to ensure that the account is not marked as `NotExisting`.
155        let info = DatabaseRef::basic_ref(&db, address).unwrap();
156        assert!(info.is_none());
157        let mut info = info.unwrap_or_default();
158        info.balance = U256::from(500u64);
159
160        // insert the modified account info
161        db.insert_account_info(address, info.clone());
162
163        let loaded = Database::basic(&mut db, address).unwrap();
164        assert!(loaded.is_some());
165        assert_eq!(loaded.unwrap(), info)
166    }
167
168    /// Demonstrates that `Database::basic` for `MemDb` will always return the `AccountInfo`
169    #[test]
170    fn mem_db_insert_basic_default() {
171        let mut db = MemDb::default();
172        let address = Address::from_word(b256!(
173            "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
174        ));
175
176        let info = Database::basic(&mut db, address).unwrap();
177        // We know info exists, as MemDb always returns `Some(AccountInfo)` due to the
178        // `EmptyDbWrapper`.
179        assert!(info.is_some());
180        let mut info = info.unwrap();
181        info.balance = U256::from(500u64);
182
183        // insert the modified account info
184        db.inner.insert_account_info(address, info.clone());
185
186        let loaded = Database::basic(&mut db, address).unwrap();
187        assert!(loaded.is_some());
188        assert_eq!(loaded.unwrap(), info)
189    }
190}