1use crate::{
4 backend::{RevertStateSnapshotAction, StateSnapshot},
5 state_snapshot::StateSnapshots,
6};
7use alloy_network::Network;
8use alloy_primitives::{Address, B256, U256};
9use alloy_rpc_types::BlockId;
10use foundry_fork_db::{BlockchainDb, DatabaseError, ForkBlockEnv, SharedBackend};
11use parking_lot::Mutex;
12use revm::{
13 Database, DatabaseCommit,
14 bytecode::Bytecode,
15 context::BlockEnv,
16 database::{AccountState, CacheDB, DatabaseRef},
17 primitives::AddressMap,
18 state::{Account, AccountInfo},
19};
20use std::sync::Arc;
21
22#[derive(Clone, Debug)]
29pub struct ForkedDatabase<N: Network, B: ForkBlockEnv = BlockEnv> {
30 backend: SharedBackend<N, B>,
34 cache_db: CacheDB<SharedBackend<N, B>>,
40 db: BlockchainDb<B>,
44 state_snapshots: Arc<Mutex<StateSnapshots<ForkDbStateSnapshot<N, B>>>>,
46}
47
48impl<N: Network, B: ForkBlockEnv> ForkedDatabase<N, B> {
49 pub fn new(backend: SharedBackend<N, B>, db: BlockchainDb<B>) -> Self {
51 Self {
52 cache_db: CacheDB::new(backend.clone()),
53 backend,
54 db,
55 state_snapshots: Arc::new(Mutex::new(Default::default())),
56 }
57 }
58
59 pub const fn database(&self) -> &CacheDB<SharedBackend<N, B>> {
60 &self.cache_db
61 }
62
63 pub const fn database_mut(&mut self) -> &mut CacheDB<SharedBackend<N, B>> {
64 &mut self.cache_db
65 }
66
67 pub const fn state_snapshots(&self) -> &Arc<Mutex<StateSnapshots<ForkDbStateSnapshot<N, B>>>> {
68 &self.state_snapshots
69 }
70
71 pub fn reset(
73 &mut self,
74 _urls: Vec<String>,
75 block_number: impl Into<BlockId>,
76 ) -> Result<(), String> {
77 self.backend.set_pinned_block(block_number).map_err(|err| err.to_string())?;
78
79 self.inner().db().clear();
83 self.cache_db = CacheDB::new(self.backend.clone());
85 trace!(target: "backend::forkdb", "Cleared database");
86 Ok(())
87 }
88
89 pub fn flush_cache(&self) {
91 self.db.cache().flush()
92 }
93
94 pub const fn inner(&self) -> &BlockchainDb<B> {
96 &self.db
97 }
98
99 pub fn create_state_snapshot(&self) -> ForkDbStateSnapshot<N, B> {
100 let db = self.db.db();
101 let state_snapshot = StateSnapshot {
102 accounts: db.accounts.read().clone(),
103 storage: db.storage.read().clone(),
104 block_hashes: db.block_hashes.read().clone(),
105 account_states: None,
106 };
107 ForkDbStateSnapshot { local: self.cache_db.clone(), state_snapshot }
108 }
109
110 pub fn insert_state_snapshot(&self) -> U256 {
111 let state_snapshot = self.create_state_snapshot();
112 let mut state_snapshots = self.state_snapshots().lock();
113 let id = state_snapshots.insert(state_snapshot);
114 trace!(target: "backend::forkdb", "Created new snapshot {}", id);
115 id
116 }
117
118 pub fn revert_state_snapshot(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool {
120 let state_snapshot = { self.state_snapshots().lock().remove(id) };
121 if let Some(state_snapshot) = state_snapshot {
122 if action.is_keep() {
123 self.state_snapshots().lock().insert_at(state_snapshot.clone(), id);
124 }
125 let ForkDbStateSnapshot {
126 local,
127 state_snapshot: StateSnapshot { accounts, storage, block_hashes, .. },
128 } = state_snapshot;
129 let db = self.inner().db();
130 {
131 let mut accounts_lock = db.accounts.write();
132 accounts_lock.clear();
133 accounts_lock.extend(accounts);
134 }
135 {
136 let mut storage_lock = db.storage.write();
137 storage_lock.clear();
138 storage_lock.extend(storage);
139 }
140 {
141 let mut block_hashes_lock = db.block_hashes.write();
142 block_hashes_lock.clear();
143 block_hashes_lock.extend(block_hashes);
144 }
145
146 self.cache_db = local;
147
148 trace!(target: "backend::forkdb", "Reverted snapshot {}", id);
149 true
150 } else {
151 warn!(target: "backend::forkdb", "No snapshot to revert for {}", id);
152 false
153 }
154 }
155}
156
157impl<N: Network, B: ForkBlockEnv> Database for ForkedDatabase<N, B> {
158 type Error = DatabaseError;
159
160 fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
161 Database::basic(&mut self.cache_db, address)
165 }
166
167 fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
168 Database::code_by_hash(&mut self.cache_db, code_hash)
169 }
170
171 fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
172 Database::storage(&mut self.cache_db, address, index)
173 }
174
175 fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
176 Database::block_hash(&mut self.cache_db, number)
177 }
178}
179
180impl<N: Network, B: ForkBlockEnv> DatabaseRef for ForkedDatabase<N, B> {
181 type Error = DatabaseError;
182
183 fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
184 self.cache_db.basic_ref(address)
185 }
186
187 fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
188 self.cache_db.code_by_hash_ref(code_hash)
189 }
190
191 fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
192 DatabaseRef::storage_ref(&self.cache_db, address, index)
193 }
194
195 fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
196 self.cache_db.block_hash_ref(number)
197 }
198}
199
200impl<N: Network, B: ForkBlockEnv> DatabaseCommit for ForkedDatabase<N, B> {
201 fn commit(&mut self, changes: AddressMap<Account>) {
202 self.database_mut().commit(changes)
203 }
204}
205
206#[derive(Clone, Debug)]
210pub struct ForkDbStateSnapshot<N: Network, B: ForkBlockEnv = BlockEnv> {
211 pub local: CacheDB<SharedBackend<N, B>>,
212 pub state_snapshot: StateSnapshot,
213}
214
215impl<N: Network, B: ForkBlockEnv> ForkDbStateSnapshot<N, B> {
216 fn storage_from_snapshot_or_backend(
218 &self,
219 address: Address,
220 index: U256,
221 ) -> Result<U256, DatabaseError> {
222 if let Some(val) = self.state_snapshot.storage.get(&address).and_then(|s| s.get(&index)) {
224 return Ok(*val);
225 }
226 DatabaseRef::storage_ref(&self.local, address, index)
228 }
229}
230
231impl<N: Network, B: ForkBlockEnv> DatabaseRef for ForkDbStateSnapshot<N, B> {
235 type Error = DatabaseError;
236
237 fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
238 match self.local.cache.accounts.get(&address) {
239 Some(account) => Ok(Some(account.info.clone())),
240 None => {
241 let mut acc = self.state_snapshot.accounts.get(&address).cloned();
242
243 if acc.is_none() {
244 acc = self.local.basic_ref(address)?;
245 }
246 Ok(acc)
247 }
248 }
249 }
250
251 fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
252 self.local.code_by_hash_ref(code_hash)
253 }
254
255 fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
256 match self.local.cache.accounts.get(&address) {
257 Some(account) => match account.storage.get(&index) {
258 Some(entry) => Ok(*entry),
259 None if matches!(
260 account.account_state,
261 AccountState::NotExisting | AccountState::StorageCleared
262 ) =>
263 {
264 Ok(U256::ZERO)
265 }
266 None => self.storage_from_snapshot_or_backend(address, index),
267 },
268 None => self.storage_from_snapshot_or_backend(address, index),
269 }
270 }
271
272 fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
273 match self.state_snapshot.block_hashes.get(&U256::from(number)).copied() {
274 None => self.local.block_hash_ref(number),
275 Some(block_hash) => Ok(block_hash),
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::backend::BlockchainDbMeta;
284 use alloy_network::AnyNetwork;
285 use alloy_provider::{ProviderBuilder, mock::Asserter};
286 use foundry_common::provider::get_http_provider;
287 use revm::database::DbAccount;
288
289 fn fork_snapshot_with_closed_backend() -> ForkDbStateSnapshot<AnyNetwork> {
290 let provider =
291 ProviderBuilder::<_, _, AnyNetwork>::default().connect_mocked_client(Asserter::new());
292 let db = BlockchainDb::new(
293 BlockchainDbMeta::new(BlockEnv::default(), "http://localhost".to_string()),
294 None,
295 );
296 let (backend, handler) = SharedBackend::new(provider, db, None);
297 drop(handler);
298 ForkDbStateSnapshot {
299 local: CacheDB::new(backend),
300 state_snapshot: StateSnapshot::default(),
301 }
302 }
303
304 #[test]
305 fn fork_db_state_snapshot_respects_local_storage_clearing() {
306 let address = Address::random();
307 let slot = U256::from(42);
308 for account_state in [AccountState::NotExisting, AccountState::StorageCleared] {
309 let mut snapshot = fork_snapshot_with_closed_backend();
310 snapshot.state_snapshot.storage.entry(address).or_default().insert(slot, U256::from(7));
311 snapshot.local.cache.accounts.insert(
312 address,
313 DbAccount { account_state: account_state.clone(), ..Default::default() },
314 );
315
316 assert_eq!(snapshot.storage_ref(address, slot).unwrap(), U256::ZERO);
317 assert_eq!(snapshot.storage_ref(address, slot + U256::ONE).unwrap(), U256::ZERO);
318
319 if account_state.is_storage_cleared() {
320 snapshot
321 .local
322 .cache
323 .accounts
324 .get_mut(&address)
325 .unwrap()
326 .storage
327 .insert(slot, U256::from(9));
328 assert_eq!(snapshot.storage_ref(address, slot).unwrap(), U256::from(9));
329 }
330 }
331 }
332
333 #[tokio::test(flavor = "multi_thread")]
336 async fn fork_db_insert_basic_default() {
337 let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint();
338 let provider = get_http_provider(rpc.clone());
339 let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc);
340
341 let db = BlockchainDb::new(meta, None);
342
343 let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await;
344
345 let mut db = ForkedDatabase::new(backend, db);
346 let address = Address::random();
347
348 let info = Database::basic(&mut db, address).unwrap();
349 assert!(info.is_some());
350 let mut info = info.unwrap();
351 info.balance = U256::from(500u64);
352
353 db.database_mut().insert_account_info(address, info.clone());
355
356 let loaded = Database::basic(&mut db, address).unwrap();
357 assert!(loaded.is_some());
358 assert_eq!(loaded.unwrap(), info);
359 }
360
361 #[test]
364 fn fork_db_state_snapshot_reads_storage_from_snapshot() {
365 let mut snapshot = fork_snapshot_with_closed_backend();
366 let address = Address::random();
367 let slot = U256::from(42u64);
368 let expected = U256::from(0xdeadbeefu64);
369
370 snapshot.state_snapshot.storage.entry(address).or_default().insert(slot, expected);
371
372 assert_eq!(snapshot.storage_ref(address, slot).unwrap(), expected);
373 snapshot.local.cache.accounts.insert(address, DbAccount::default());
374 assert_eq!(snapshot.storage_ref(address, slot).unwrap(), expected);
375 }
376
377 #[tokio::test(flavor = "multi_thread")]
378 async fn fork_db_revert_invalidates_newer_snapshots() {
379 let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint();
380 let provider = get_http_provider(rpc.clone());
381 let meta = BlockchainDbMeta::new(BlockEnv::default(), rpc);
382 let db = BlockchainDb::new(meta, None);
383 let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await;
384 let mut db = ForkedDatabase::new(backend, db);
385
386 let first = db.insert_state_snapshot();
387 let second = db.insert_state_snapshot();
388
389 assert!(db.revert_state_snapshot(first, RevertStateSnapshotAction::RevertRemove));
390 assert!(!db.revert_state_snapshot(second, RevertStateSnapshotAction::RevertRemove));
391 }
392}