Skip to main content

foundry_evm_core/backend/
cow.rs

1//! A wrapper around `Backend` that is clone-on-write used for fuzzing.
2
3use super::BackendError;
4use crate::{
5    FoundryInspectorExt,
6    backend::{
7        Backend, DatabaseExt, JournaledState, LocalForkId, RevertStateSnapshotAction,
8        diagnostic::RevertDiagnostic,
9    },
10    evm::{
11        EvmEnvFor, FoundryContextFor, FoundryEvmFactory, FoundryEvmNetwork, HaltReasonFor, SpecFor,
12        TxEnvFor,
13    },
14    fork::{CreateFork, ForkId},
15};
16use alloy_evm::Evm;
17use alloy_genesis::GenesisAccount;
18use alloy_primitives::{Address, B256, TxKind, U256};
19use eyre::WrapErr;
20use foundry_fork_db::DatabaseError;
21use revm::{
22    Database, DatabaseCommit,
23    bytecode::Bytecode,
24    context::{ContextTr, Transaction},
25    context_interface::result::ResultAndState,
26    database::DatabaseRef,
27    primitives::AddressMap,
28    state::{Account, AccountInfo, EvmState},
29};
30use std::{borrow::Cow, collections::BTreeMap, fmt::Debug};
31
32/// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called.
33///
34/// Any changes made during its existence that affect the caching layer of the underlying Database
35/// will result in a clone of the initial Database. Therefore, this backend type is basically
36/// a clone-on-write `Backend`, where cloning is only necessary if cheatcodes will modify the
37/// `Backend`
38///
39/// Entire purpose of this type is for fuzzing. A test function fuzzer will repeatedly execute the
40/// function via immutable raw (no state changes) calls.
41///
42/// **N.B.**: we're assuming cheatcodes that alter the state (like multi fork swapping) are niche.
43/// If they executed, it will require a clone of the initial input database.
44/// This way we can support these cheatcodes cheaply without adding overhead for tests that
45/// don't make use of them. Alternatively each test case would require its own `Backend` clone,
46/// which would add significant overhead for large fuzz sets even if the Database is not big after
47/// setup.
48pub struct CowBackend<'a, FEN: FoundryEvmNetwork> {
49    /// The underlying `Backend`.
50    ///
51    /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state.
52    pub backend: Cow<'a, Backend<FEN>>,
53    /// Pending initialization params for the backend on first mutable access.
54    /// `None` means the backend has already been initialized for the current call.
55    pending_init: Option<(SpecFor<FEN>, Address, TxKind)>,
56}
57
58impl<FEN: FoundryEvmNetwork> Clone for CowBackend<'_, FEN> {
59    fn clone(&self) -> Self {
60        Self { backend: self.backend.clone(), pending_init: self.pending_init }
61    }
62}
63
64impl<FEN: FoundryEvmNetwork> Debug for CowBackend<'_, FEN> {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        f.debug_struct("CowBackend")
67            .field("backend", &self.backend)
68            .field("pending_init", &self.pending_init)
69            .finish()
70    }
71}
72
73impl<'a, FEN: FoundryEvmNetwork> CowBackend<'a, FEN> {
74    /// Creates a new `CowBackend` with the given `Backend`.
75    pub const fn new_borrowed(backend: &'a Backend<FEN>) -> Self {
76        Self { backend: Cow::Borrowed(backend), pending_init: None }
77    }
78
79    /// Executes the configured transaction of the `env` without committing state changes
80    ///
81    /// Note: in case there are any cheatcodes executed that modify the environment, this will
82    /// update the given `env` with the new values.
83    #[instrument(name = "inspect", level = "debug", skip_all)]
84    pub fn inspect<I: for<'db> FoundryInspectorExt<FoundryContextFor<'db, FEN>>>(
85        &mut self,
86        evm_env: &mut EvmEnvFor<FEN>,
87        tx_env: &mut TxEnvFor<FEN>,
88        inspector: I,
89    ) -> eyre::Result<ResultAndState<HaltReasonFor<FEN>>> {
90        // this is a new call to inspect with a new env, so even if we've cloned the backend
91        // already, we reset the initialized state
92        self.pending_init = Some((evm_env.cfg_env.spec, tx_env.caller(), tx_env.kind()));
93
94        let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector(
95            self,
96            evm_env.clone(),
97            inspector,
98        );
99
100        let res = evm.transact(tx_env.clone()).wrap_err("EVM error")?;
101
102        *tx_env = evm.tx().clone();
103        *evm_env = evm.finish().1;
104
105        Ok(res)
106    }
107
108    /// Returns whether there was a state snapshot failure in the backend.
109    ///
110    /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs.
111    pub fn has_state_snapshot_failure(&self) -> bool {
112        self.backend.has_state_snapshot_failure()
113    }
114
115    /// Returns a mutable instance of the Backend.
116    ///
117    /// If this is the first time this is called, the backed is cloned and initialized.
118    fn backend_mut(&mut self) -> &mut Backend<FEN> {
119        if let Some((spec_id, caller, tx_kind)) = self.pending_init.take() {
120            let backend = self.backend.to_mut();
121            backend.initialize(spec_id, caller, tx_kind);
122            return backend;
123        }
124        self.backend.to_mut()
125    }
126
127    /// Returns a mutable instance of the Backend if it is initialized.
128    fn initialized_backend_mut(&mut self) -> Option<&mut Backend<FEN>> {
129        if self.pending_init.is_none() {
130            return Some(self.backend.to_mut());
131        }
132        None
133    }
134}
135
136impl<FEN: FoundryEvmNetwork> DatabaseExt<FEN::EvmFactory> for CowBackend<'_, FEN> {
137    fn snapshot_state(
138        &mut self,
139        journaled_state: &JournaledState,
140        evm_env: &EvmEnvFor<FEN>,
141    ) -> U256 {
142        self.backend_mut().snapshot_state(journaled_state, evm_env)
143    }
144
145    fn revert_state(
146        &mut self,
147        id: U256,
148        journaled_state: &JournaledState,
149        evm_env: &mut EvmEnvFor<FEN>,
150        caller: Address,
151        action: RevertStateSnapshotAction,
152    ) -> Option<JournaledState> {
153        self.backend_mut().revert_state(id, journaled_state, evm_env, caller, action)
154    }
155
156    fn delete_state_snapshot(&mut self, id: U256) -> bool {
157        // delete state snapshot requires a previous snapshot to be initialized
158        if let Some(backend) = self.initialized_backend_mut() {
159            return backend.delete_state_snapshot(id);
160        }
161        false
162    }
163
164    fn delete_state_snapshots(&mut self) {
165        if let Some(backend) = self.initialized_backend_mut() {
166            backend.delete_state_snapshots()
167        }
168    }
169
170    fn create_fork(&mut self, fork: CreateFork) -> eyre::Result<LocalForkId> {
171        self.backend.to_mut().create_fork(fork)
172    }
173
174    fn create_fork_at_transaction(
175        &mut self,
176        fork: CreateFork,
177        transaction: B256,
178    ) -> eyre::Result<LocalForkId> {
179        self.backend.to_mut().create_fork_at_transaction(fork, transaction)
180    }
181
182    fn select_fork(
183        &mut self,
184        id: LocalForkId,
185        evm_env: &mut EvmEnvFor<FEN>,
186        tx_env: &mut TxEnvFor<FEN>,
187        journaled_state: &mut JournaledState,
188    ) -> eyre::Result<()> {
189        self.backend_mut().select_fork(id, evm_env, tx_env, journaled_state)
190    }
191
192    fn roll_fork(
193        &mut self,
194        id: Option<LocalForkId>,
195        block_number: u64,
196        evm_env: &mut EvmEnvFor<FEN>,
197        journaled_state: &mut JournaledState,
198    ) -> eyre::Result<()> {
199        self.backend_mut().roll_fork(id, block_number, evm_env, journaled_state)
200    }
201
202    fn roll_fork_to_transaction(
203        &mut self,
204        id: Option<LocalForkId>,
205        transaction: B256,
206        evm_env: &mut EvmEnvFor<FEN>,
207        journaled_state: &mut JournaledState,
208    ) -> eyre::Result<()> {
209        self.backend_mut().roll_fork_to_transaction(id, transaction, evm_env, journaled_state)
210    }
211
212    fn transact(
213        &mut self,
214        id: Option<LocalForkId>,
215        transaction: B256,
216        evm_env: EvmEnvFor<FEN>,
217        journaled_state: &mut JournaledState,
218        inspector: &mut dyn for<'db> FoundryInspectorExt<
219            <FEN::EvmFactory as FoundryEvmFactory>::FoundryContext<'db>,
220        >,
221    ) -> eyre::Result<()> {
222        self.backend_mut().transact(id, transaction, evm_env, journaled_state, inspector)
223    }
224
225    fn transact_from_tx(
226        &mut self,
227        tx_env: TxEnvFor<FEN>,
228        evm_env: EvmEnvFor<FEN>,
229        journaled_state: &mut JournaledState,
230        inspector: &mut dyn for<'db> FoundryInspectorExt<
231            <FEN::EvmFactory as FoundryEvmFactory>::FoundryContext<'db>,
232        >,
233    ) -> eyre::Result<()> {
234        self.backend_mut().transact_from_tx(tx_env, evm_env, journaled_state, inspector)
235    }
236
237    fn active_fork_id(&self) -> Option<LocalForkId> {
238        self.backend.active_fork_id()
239    }
240
241    fn active_fork_url(&self) -> Option<String> {
242        self.backend.active_fork_url()
243    }
244
245    fn ensure_fork(&self, id: Option<LocalForkId>) -> eyre::Result<LocalForkId> {
246        self.backend.ensure_fork(id)
247    }
248
249    fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> {
250        self.backend.ensure_fork_id(id)
251    }
252
253    fn diagnose_revert(&self, callee: Address, evm_state: &EvmState) -> Option<RevertDiagnostic> {
254        self.backend.diagnose_revert(callee, evm_state)
255    }
256
257    fn load_allocs(
258        &mut self,
259        allocs: &BTreeMap<Address, GenesisAccount>,
260        journaled_state: &mut JournaledState,
261    ) -> Result<(), BackendError> {
262        self.backend.to_mut().load_allocs(allocs, journaled_state)
263    }
264
265    fn clone_account(
266        &mut self,
267        source: &GenesisAccount,
268        target: &Address,
269        journaled_state: &mut JournaledState,
270    ) -> Result<(), BackendError> {
271        self.backend.to_mut().clone_account(source, target, journaled_state)
272    }
273
274    fn is_persistent(&self, acc: &Address) -> bool {
275        self.backend.is_persistent(acc)
276    }
277
278    fn remove_persistent_account(&mut self, account: &Address) -> bool {
279        self.backend.to_mut().remove_persistent_account(account)
280    }
281
282    fn add_persistent_account(&mut self, account: Address) -> bool {
283        self.backend.to_mut().add_persistent_account(account)
284    }
285
286    fn allow_cheatcode_access(&mut self, account: Address) -> bool {
287        self.backend.to_mut().allow_cheatcode_access(account)
288    }
289
290    fn revoke_cheatcode_access(&mut self, account: &Address) -> bool {
291        self.backend.to_mut().revoke_cheatcode_access(account)
292    }
293
294    fn has_cheatcode_access(&self, account: &Address) -> bool {
295        self.backend.has_cheatcode_access(account)
296    }
297
298    fn set_blockhash(&mut self, block_number: U256, block_hash: B256) {
299        self.backend.to_mut().set_blockhash(block_number, block_hash);
300    }
301}
302
303impl<FEN: FoundryEvmNetwork> DatabaseRef for CowBackend<'_, FEN> {
304    type Error = DatabaseError;
305
306    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
307        DatabaseRef::basic_ref(self.backend.as_ref(), address)
308    }
309
310    fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
311        DatabaseRef::code_by_hash_ref(self.backend.as_ref(), code_hash)
312    }
313
314    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
315        DatabaseRef::storage_ref(self.backend.as_ref(), address, index)
316    }
317
318    fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
319        DatabaseRef::block_hash_ref(self.backend.as_ref(), number)
320    }
321}
322
323impl<FEN: FoundryEvmNetwork> Database for CowBackend<'_, FEN> {
324    type Error = DatabaseError;
325
326    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
327        DatabaseRef::basic_ref(self, address)
328    }
329
330    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
331        DatabaseRef::code_by_hash_ref(self, code_hash)
332    }
333
334    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
335        DatabaseRef::storage_ref(self, address, index)
336    }
337
338    fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
339        DatabaseRef::block_hash_ref(self, number)
340    }
341}
342
343impl<FEN: FoundryEvmNetwork> DatabaseCommit for CowBackend<'_, FEN> {
344    fn commit(&mut self, changes: AddressMap<Account>) {
345        self.backend.to_mut().commit(changes)
346    }
347}