Skip to main content

foundry_evm_core/backend/
strategy.rs

1use std::{any::Any, fmt::Debug};
2
3use crate::{
4    Env, InspectorExt,
5    backend::{JournaledState, update_state},
6    env::AsEnvMut,
7    evm::new_evm_with_inspector,
8    utils::configure_tx_req_env,
9};
10
11use super::{Backend, BackendInner, Fork, ForkDB, FoundryEvmInMemoryDB};
12use alloy_evm::Evm;
13use alloy_primitives::Address;
14use alloy_rpc_types::TransactionRequest;
15use eyre::{Context, Result};
16use revm::{
17    DatabaseCommit, DatabaseRef, context_interface::result::ResultAndState, database::CacheDB,
18};
19use serde::{Deserialize, Serialize};
20
21/// Context for [BackendStrategyRunner].
22pub trait BackendStrategyContext: Debug + Send + Sync + Any {
23    /// Clone the strategy context.
24    fn new_cloned(&self) -> Box<dyn BackendStrategyContext>;
25    /// Alias as immutable reference of [Any].
26    fn as_any_ref(&self) -> &dyn Any;
27    /// Alias as mutable reference of [Any].
28    fn as_any_mut(&mut self) -> &mut dyn Any;
29}
30
31impl BackendStrategyContext for () {
32    fn new_cloned(&self) -> Box<dyn BackendStrategyContext> {
33        Box::new(())
34    }
35
36    fn as_any_ref(&self) -> &dyn Any {
37        self
38    }
39
40    fn as_any_mut(&mut self) -> &mut dyn Any {
41        self
42    }
43}
44
45/// Strategy for [super::Backend].
46#[derive(Debug)]
47pub struct BackendStrategy {
48    /// Strategy runner.
49    pub runner: &'static dyn BackendStrategyRunner,
50    /// Strategy context.
51    pub context: Box<dyn BackendStrategyContext>,
52}
53
54impl BackendStrategy {
55    /// Create a new instance of [BackendStrategy]
56    pub fn new_evm() -> Self {
57        Self { runner: &EvmBackendStrategyRunner, context: Box::new(()) }
58    }
59}
60
61impl Clone for BackendStrategy {
62    fn clone(&self) -> Self {
63        Self { runner: self.runner, context: self.context.new_cloned() }
64    }
65}
66
67pub trait BackendStrategyRunner: Debug + Send + Sync {
68    fn inspect(
69        &self,
70        backend: &mut Backend,
71        env: &mut Env,
72        inspector: &mut dyn InspectorExt,
73        inspect_ctx: Box<dyn Any>,
74    ) -> Result<ResultAndState>;
75
76    /// When creating or switching forks, we update the AccountInfo of the contract
77    fn update_fork_db(
78        &self,
79        ctx: &mut dyn BackendStrategyContext,
80        active_fork: Option<&Fork>,
81        mem_db: &FoundryEvmInMemoryDB,
82        backend_inner: &BackendInner,
83        active_journaled_state: &mut JournaledState,
84        target_fork: &mut Fork,
85    );
86
87    /// Clones the account data from the `active_journaled_state` into the `fork_journaled_state`
88    fn merge_journaled_state_data(
89        &self,
90        ctx: &mut dyn BackendStrategyContext,
91        addr: Address,
92        active_journaled_state: &JournaledState,
93        fork_journaled_state: &mut JournaledState,
94    );
95
96    fn merge_db_account_data(
97        &self,
98        ctx: &mut dyn BackendStrategyContext,
99        addr: Address,
100        active: &ForkDB,
101        fork_db: &mut ForkDB,
102    );
103
104    fn transact_from_tx(
105        &self,
106        backend: &mut Backend,
107        tx: &TransactionRequest,
108        env: Env,
109        journaled_state: &mut JournaledState,
110        inspector: &mut dyn InspectorExt,
111        inspect_ctx: Box<dyn Any>,
112    ) -> eyre::Result<()>;
113}
114
115#[derive(Debug, Default, Clone, Serialize, Deserialize)]
116pub struct EvmBackendStrategyRunner;
117
118impl BackendStrategyRunner for EvmBackendStrategyRunner {
119    fn inspect(
120        &self,
121        backend: &mut Backend,
122        env: &mut Env,
123        inspector: &mut dyn InspectorExt,
124        _inspect_ctx: Box<dyn Any>,
125    ) -> Result<ResultAndState> {
126        let mut evm = crate::evm::new_evm_with_inspector(backend, env.to_owned(), inspector);
127
128        let res = evm.transact(env.tx.clone()).wrap_err("EVM error")?;
129
130        *env = evm.as_env_mut().to_owned();
131
132        Ok(res)
133    }
134
135    fn update_fork_db(
136        &self,
137        _ctx: &mut dyn BackendStrategyContext,
138        active_fork: Option<&Fork>,
139        mem_db: &FoundryEvmInMemoryDB,
140        backend_inner: &BackendInner,
141        active_journaled_state: &mut JournaledState,
142        target_fork: &mut Fork,
143    ) {
144        self.update_fork_db_contracts(
145            active_fork,
146            mem_db,
147            backend_inner,
148            active_journaled_state,
149            target_fork,
150        )
151    }
152
153    fn merge_journaled_state_data(
154        &self,
155        _ctx: &mut dyn BackendStrategyContext,
156        addr: Address,
157        active_journaled_state: &JournaledState,
158        fork_journaled_state: &mut JournaledState,
159    ) {
160        EvmBackendMergeStrategy::merge_journaled_state_data(
161            addr,
162            active_journaled_state,
163            fork_journaled_state,
164        );
165    }
166
167    fn merge_db_account_data(
168        &self,
169        _ctx: &mut dyn BackendStrategyContext,
170        addr: Address,
171        active: &ForkDB,
172        fork_db: &mut ForkDB,
173    ) {
174        EvmBackendMergeStrategy::merge_db_account_data(addr, active, fork_db);
175    }
176
177    fn transact_from_tx(
178        &self,
179        backend: &mut Backend,
180        tx: &TransactionRequest,
181        mut env: Env,
182        journaled_state: &mut JournaledState,
183        inspector: &mut dyn InspectorExt,
184        _inspect_ctx: Box<dyn Any>,
185    ) -> eyre::Result<()> {
186        backend.commit(journaled_state.state.clone());
187
188        let res = {
189            configure_tx_req_env(&mut env.as_env_mut(), tx, None)?;
190
191            let mut db = backend.clone();
192            let mut evm = new_evm_with_inspector(&mut db, env.to_owned(), inspector);
193            evm.journaled_state.depth = journaled_state.depth + 1;
194            evm.transact(env.tx)?
195        };
196
197        backend.commit(res.state);
198        update_state(&mut journaled_state.state, backend, None)?;
199
200        Ok(())
201    }
202}
203
204impl EvmBackendStrategyRunner {
205    /// Merges the state of all `accounts` from the currently active db into the given `fork`
206    pub(crate) fn update_fork_db_contracts(
207        &self,
208        active_fork: Option<&Fork>,
209        mem_db: &FoundryEvmInMemoryDB,
210        backend_inner: &BackendInner,
211        active_journaled_state: &mut JournaledState,
212        target_fork: &mut Fork,
213    ) {
214        let accounts = backend_inner.persistent_accounts.iter().copied();
215        if let Some(db) = active_fork.map(|f| &f.db) {
216            EvmBackendMergeStrategy::merge_account_data(
217                accounts,
218                db,
219                active_journaled_state,
220                target_fork,
221            )
222        } else {
223            EvmBackendMergeStrategy::merge_account_data(
224                accounts,
225                mem_db,
226                active_journaled_state,
227                target_fork,
228            )
229        }
230    }
231}
232
233pub struct EvmBackendMergeStrategy;
234impl EvmBackendMergeStrategy {
235    /// Clones the data of the given `accounts` from the `active` database into the `fork_db`
236    /// This includes the data held in storage (`CacheDB`) and kept in the `JournaledState`.
237    pub fn merge_account_data<ExtDB: DatabaseRef>(
238        accounts: impl IntoIterator<Item = Address>,
239        active: &CacheDB<ExtDB>,
240        active_journaled_state: &mut JournaledState,
241        target_fork: &mut Fork,
242    ) {
243        for addr in accounts.into_iter() {
244            Self::merge_db_account_data(addr, active, &mut target_fork.db);
245            Self::merge_journaled_state_data(
246                addr,
247                active_journaled_state,
248                &mut target_fork.journaled_state,
249            );
250        }
251
252        *active_journaled_state = target_fork.journaled_state.clone();
253    }
254
255    /// Clones the account data from the `active_journaled_state`  into the `fork_journaled_state`
256    pub fn merge_journaled_state_data(
257        addr: Address,
258        active_journaled_state: &JournaledState,
259        fork_journaled_state: &mut JournaledState,
260    ) {
261        if let Some(mut acc) = active_journaled_state.state.get(&addr).cloned() {
262            trace!(?addr, "updating journaled_state account data");
263            if let Some(fork_account) = fork_journaled_state.state.get_mut(&addr) {
264                // This will merge the fork's tracked storage with active storage and update values
265                fork_account.storage.extend(std::mem::take(&mut acc.storage));
266                // swap them so we can insert the account as whole in the next step
267                std::mem::swap(&mut fork_account.storage, &mut acc.storage);
268            }
269            fork_journaled_state.state.insert(addr, acc);
270        }
271    }
272
273    /// Clones the account data from the `active` db into the `ForkDB`
274    pub fn merge_db_account_data<ExtDB: DatabaseRef>(
275        addr: Address,
276        active: &CacheDB<ExtDB>,
277        fork_db: &mut ForkDB,
278    ) {
279        let mut acc = if let Some(acc) = active.cache.accounts.get(&addr).cloned() {
280            acc
281        } else {
282            // Account does not exist
283            return;
284        };
285
286        if let Some(code) = active.cache.contracts.get(&acc.info.code_hash).cloned() {
287            fork_db.cache.contracts.insert(acc.info.code_hash, code);
288        }
289
290        if let Some(fork_account) = fork_db.cache.accounts.get_mut(&addr) {
291            // This will merge the fork's tracked storage with active storage and update values
292            fork_account.storage.extend(std::mem::take(&mut acc.storage));
293            // swap them so we can insert the account as whole in the next step
294            std::mem::swap(&mut fork_account.storage, &mut acc.storage);
295        }
296
297        fork_db.cache.accounts.insert(addr, acc);
298    }
299}