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