Skip to main content

foundry_cheatcodes/evm/
fork.rs

1use crate::{
2    Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, EthCheatCtx, Result, Vm::*,
3    json::json_value_to_token,
4};
5use alloy_dyn_abi::DynSolValue;
6use alloy_evm::EvmEnv;
7use alloy_network::AnyNetwork;
8use alloy_primitives::{B256, U256};
9use alloy_provider::Provider;
10use alloy_rpc_types::Filter;
11use alloy_sol_types::SolValue;
12use foundry_common::provider::ProviderBuilder;
13use foundry_evm_core::{FoundryContextExt, backend::JournaledState, fork::CreateFork};
14use revm::context::{Cfg, ContextTr};
15
16impl Cheatcode for activeForkCall {
17    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
18        &self,
19        ccx: &mut CheatsCtxt<'_, CTX>,
20    ) -> Result {
21        let Self {} = self;
22        ccx.ecx
23            .db()
24            .active_fork_id()
25            .map(|id| id.abi_encode())
26            .ok_or_else(|| fmt_err!("no active fork"))
27    }
28}
29
30impl Cheatcode for createFork_0Call {
31    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
32        let Self { urlOrAlias } = self;
33        create_fork(ccx, urlOrAlias, None)
34    }
35}
36
37impl Cheatcode for createFork_1Call {
38    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
39        let Self { urlOrAlias, blockNumber } = self;
40        create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
41    }
42}
43
44impl Cheatcode for createFork_2Call {
45    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
46        let Self { urlOrAlias, txHash } = self;
47        create_fork_at_transaction(ccx, urlOrAlias, txHash)
48    }
49}
50
51impl Cheatcode for createSelectFork_0Call {
52    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
53        let Self { urlOrAlias } = self;
54        create_select_fork(ccx, urlOrAlias, None)
55    }
56}
57
58impl Cheatcode for createSelectFork_1Call {
59    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
60        let Self { urlOrAlias, blockNumber } = self;
61        create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
62    }
63}
64
65impl Cheatcode for createSelectFork_2Call {
66    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
67        let Self { urlOrAlias, txHash } = self;
68        create_select_fork_at_transaction(ccx, urlOrAlias, txHash)
69    }
70}
71
72impl Cheatcode for rollFork_0Call {
73    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
74        let Self { blockNumber } = self;
75        persist_caller(ccx);
76        fork_env_op(ccx, |db, evm_env, tx_env, inner| {
77            db.roll_fork(None, (*blockNumber).to(), evm_env, tx_env, inner)
78        })
79    }
80}
81
82impl Cheatcode for rollFork_1Call {
83    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
84        let Self { txHash } = self;
85        persist_caller(ccx);
86        fork_env_op(ccx, |db, evm_env, tx_env, inner| {
87            db.roll_fork_to_transaction(None, *txHash, evm_env, tx_env, inner)
88        })
89    }
90}
91
92impl Cheatcode for rollFork_2Call {
93    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
94        let Self { forkId, blockNumber } = self;
95        persist_caller(ccx);
96        fork_env_op(ccx, |db, evm_env, tx_env, inner| {
97            db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, tx_env, inner)
98        })
99    }
100}
101
102impl Cheatcode for rollFork_3Call {
103    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
104        let Self { forkId, txHash } = self;
105        persist_caller(ccx);
106        fork_env_op(ccx, |db, evm_env, tx_env, inner| {
107            db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, tx_env, inner)
108        })
109    }
110}
111
112impl Cheatcode for selectForkCall {
113    fn apply_stateful<CTX: EthCheatCtx>(&self, ccx: &mut CheatsCtxt<'_, CTX>) -> Result {
114        let Self { forkId } = self;
115        persist_caller(ccx);
116        check_broadcast(ccx.state)?;
117        fork_env_op(ccx, |db, evm_env, tx_env, inner| {
118            db.select_fork(*forkId, evm_env, tx_env, inner)
119        })
120    }
121}
122
123impl Cheatcode for transact_0Call {
124    fn apply_full<CTX: FoundryContextExt>(
125        &self,
126        ccx: &mut CheatsCtxt<'_, CTX>,
127        executor: &mut dyn CheatcodesExecutor<CTX>,
128    ) -> Result {
129        let Self { txHash } = *self;
130        transact(ccx, executor, txHash, None)
131    }
132}
133
134impl Cheatcode for transact_1Call {
135    fn apply_full<CTX: FoundryContextExt>(
136        &self,
137        ccx: &mut CheatsCtxt<'_, CTX>,
138        executor: &mut dyn CheatcodesExecutor<CTX>,
139    ) -> Result {
140        let Self { forkId, txHash } = *self;
141        transact(ccx, executor, txHash, Some(forkId))
142    }
143}
144
145impl Cheatcode for allowCheatcodesCall {
146    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
147        &self,
148        ccx: &mut CheatsCtxt<'_, CTX>,
149    ) -> Result {
150        let Self { account } = self;
151        ccx.ecx.db_mut().allow_cheatcode_access(*account);
152        Ok(Default::default())
153    }
154}
155
156impl Cheatcode for makePersistent_0Call {
157    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
158        &self,
159        ccx: &mut CheatsCtxt<'_, CTX>,
160    ) -> Result {
161        let Self { account } = self;
162        ccx.ecx.db_mut().add_persistent_account(*account);
163        Ok(Default::default())
164    }
165}
166
167impl Cheatcode for makePersistent_1Call {
168    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
169        &self,
170        ccx: &mut CheatsCtxt<'_, CTX>,
171    ) -> Result {
172        let Self { account0, account1 } = self;
173        ccx.ecx.db_mut().add_persistent_account(*account0);
174        ccx.ecx.db_mut().add_persistent_account(*account1);
175        Ok(Default::default())
176    }
177}
178
179impl Cheatcode for makePersistent_2Call {
180    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
181        &self,
182        ccx: &mut CheatsCtxt<'_, CTX>,
183    ) -> Result {
184        let Self { account0, account1, account2 } = self;
185        ccx.ecx.db_mut().add_persistent_account(*account0);
186        ccx.ecx.db_mut().add_persistent_account(*account1);
187        ccx.ecx.db_mut().add_persistent_account(*account2);
188        Ok(Default::default())
189    }
190}
191
192impl Cheatcode for makePersistent_3Call {
193    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
194        &self,
195        ccx: &mut CheatsCtxt<'_, CTX>,
196    ) -> Result {
197        let Self { accounts } = self;
198        for account in accounts {
199            ccx.ecx.db_mut().add_persistent_account(*account);
200        }
201        Ok(Default::default())
202    }
203}
204
205impl Cheatcode for revokePersistent_0Call {
206    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
207        &self,
208        ccx: &mut CheatsCtxt<'_, CTX>,
209    ) -> Result {
210        let Self { account } = self;
211        ccx.ecx.db_mut().remove_persistent_account(account);
212        Ok(Default::default())
213    }
214}
215
216impl Cheatcode for revokePersistent_1Call {
217    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
218        &self,
219        ccx: &mut CheatsCtxt<'_, CTX>,
220    ) -> Result {
221        let Self { accounts } = self;
222        for account in accounts {
223            ccx.ecx.db_mut().remove_persistent_account(account);
224        }
225        Ok(Default::default())
226    }
227}
228
229impl Cheatcode for isPersistentCall {
230    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
231        &self,
232        ccx: &mut CheatsCtxt<'_, CTX>,
233    ) -> Result {
234        let Self { account } = self;
235        Ok(ccx.ecx.db().is_persistent(account).abi_encode())
236    }
237}
238
239impl Cheatcode for rpc_0Call {
240    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
241        &self,
242        ccx: &mut CheatsCtxt<'_, CTX>,
243    ) -> Result {
244        let Self { method, params } = self;
245        let url =
246            ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
247        rpc_call(&url, method, params)
248    }
249}
250
251impl Cheatcode for rpc_1Call {
252    fn apply(&self, state: &mut Cheatcodes) -> Result {
253        let Self { urlOrAlias, method, params } = self;
254        let url = state.config.rpc_endpoint(urlOrAlias)?.url()?;
255        rpc_call(&url, method, params)
256    }
257}
258
259impl Cheatcode for eth_getLogsCall {
260    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
261        &self,
262        ccx: &mut CheatsCtxt<'_, CTX>,
263    ) -> Result {
264        let Self { fromBlock, toBlock, target, topics } = self;
265        let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock))
266        else {
267            bail!("blocks in block range must be less than 2^64")
268        };
269
270        if topics.len() > 4 {
271            bail!("topics array must contain at most 4 elements")
272        }
273
274        let url =
275            ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
276        let provider = ProviderBuilder::<AnyNetwork>::new(&url).build()?;
277        let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block);
278        for (i, &topic) in topics.iter().enumerate() {
279            filter.topics[i] = topic.into();
280        }
281
282        let logs = foundry_common::block_on(provider.get_logs(&filter))
283            .map_err(|e| fmt_err!("failed to get logs: {e}"))?;
284
285        let eth_logs = logs
286            .into_iter()
287            .map(|log| EthGetLogs {
288                emitter: log.address(),
289                topics: log.topics().to_vec(),
290                data: log.inner.data.data,
291                blockHash: log.block_hash.unwrap_or_default(),
292                blockNumber: log.block_number.unwrap_or_default(),
293                transactionHash: log.transaction_hash.unwrap_or_default(),
294                transactionIndex: log.transaction_index.unwrap_or_default(),
295                logIndex: U256::from(log.log_index.unwrap_or_default()),
296                removed: log.removed,
297            })
298            .collect::<Vec<_>>();
299
300        Ok(eth_logs.abi_encode())
301    }
302}
303
304impl Cheatcode for getRawBlockHeaderCall {
305    fn apply_stateful<CTX: ContextTr<Db: DatabaseExt>>(
306        &self,
307        ccx: &mut CheatsCtxt<'_, CTX>,
308    ) -> Result {
309        let Self { blockNumber } = self;
310        let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork"))?;
311        let provider = ProviderBuilder::<AnyNetwork>::new(&url).build()?;
312        let block_number = u64::try_from(blockNumber)
313            .map_err(|_| fmt_err!("block number must be less than 2^64"))?;
314        let block =
315            foundry_common::block_on(async move { provider.get_block(block_number.into()).await })
316                .map_err(|e| fmt_err!("failed to get block: {e}"))?
317                .ok_or_else(|| fmt_err!("block {block_number} not found"))?;
318
319        let header: alloy_consensus::Header = block
320            .into_inner()
321            .header
322            .inner
323            .try_into_header()
324            .map_err(|e| fmt_err!("failed to convert to header: {e}"))?;
325        Ok(alloy_rlp::encode(&header).abi_encode())
326    }
327}
328
329/// Creates and then also selects the new fork
330fn create_select_fork<CTX: EthCheatCtx>(
331    ccx: &mut CheatsCtxt<'_, CTX>,
332    url_or_alias: &str,
333    block: Option<u64>,
334) -> Result {
335    check_broadcast(ccx.state)?;
336
337    let fork = create_fork_request(ccx, url_or_alias, block)?;
338    fork_env_op(ccx, |db, evm_env, tx_env, inner| {
339        db.create_select_fork(fork, evm_env, tx_env, inner)
340    })
341}
342
343/// Creates a new fork
344fn create_fork<CTX: EthCheatCtx>(
345    ccx: &mut CheatsCtxt<'_, CTX>,
346    url_or_alias: &str,
347    block: Option<u64>,
348) -> Result {
349    let fork = create_fork_request(ccx, url_or_alias, block)?;
350    let id = ccx.ecx.db_mut().create_fork(fork)?;
351    Ok(id.abi_encode())
352}
353
354/// Creates and then also selects the new fork at the given transaction
355fn create_select_fork_at_transaction<CTX: EthCheatCtx>(
356    ccx: &mut CheatsCtxt<'_, CTX>,
357    url_or_alias: &str,
358    transaction: &B256,
359) -> Result {
360    check_broadcast(ccx.state)?;
361
362    let fork = create_fork_request(ccx, url_or_alias, None)?;
363    fork_env_op(ccx, |db, evm_env, tx_env, inner| {
364        db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction)
365    })
366}
367
368/// Creates a new fork at the given transaction
369fn create_fork_at_transaction<CTX: EthCheatCtx>(
370    ccx: &mut CheatsCtxt<'_, CTX>,
371    url_or_alias: &str,
372    transaction: &B256,
373) -> Result {
374    let fork = create_fork_request(ccx, url_or_alias, None)?;
375    let id = ccx.ecx.db_mut().create_fork_at_transaction(fork, *transaction)?;
376    Ok(id.abi_encode())
377}
378
379/// Creates the request object for a new fork request
380fn create_fork_request<CTX: EthCheatCtx>(
381    ccx: &mut CheatsCtxt<'_, CTX>,
382    url_or_alias: &str,
383    block: Option<u64>,
384) -> Result<CreateFork> {
385    persist_caller(ccx);
386
387    let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?;
388    let url = rpc_endpoint.url()?;
389    let mut evm_opts = ccx.state.config.evm_opts.clone();
390    evm_opts.fork_block_number = block;
391    evm_opts.fork_retries = rpc_endpoint.config.retries;
392    evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff;
393    if let Some(Ok(auth)) = rpc_endpoint.auth {
394        evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]);
395    }
396    let fork = CreateFork {
397        enable_caching: !ccx.state.config.no_storage_caching
398            && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url),
399        url,
400        evm_env: EvmEnv {
401            cfg_env: ccx.ecx.cfg_mut().clone(),
402            block_env: ccx.ecx.block_mut().clone(),
403        },
404        evm_opts,
405    };
406    Ok(fork)
407}
408
409/// Clones the EVM and tx environments, runs a fork operation that may modify them, then writes
410/// them back. This is the common pattern for all fork-switching cheatcodes (rollFork, selectFork,
411/// createSelectFork).
412fn fork_env_op<CTX: EthCheatCtx, T: SolValue>(
413    ccx: &mut CheatsCtxt<'_, CTX>,
414    f: impl FnOnce(
415        &mut dyn DatabaseExt<CTX::Block, CTX::Tx, <CTX::Cfg as Cfg>::Spec>,
416        &mut EvmEnv<<CTX::Cfg as Cfg>::Spec, CTX::Block>,
417        &mut CTX::Tx,
418        &mut JournaledState,
419    ) -> eyre::Result<T>,
420) -> Result {
421    let mut evm_env = ccx.ecx.evm_clone();
422    let mut tx_env = ccx.ecx.tx_clone();
423    let (db, inner) = ccx.ecx.db_journal_inner_mut();
424    let result = f(db, &mut evm_env, &mut tx_env, inner)?;
425    ccx.ecx.set_evm(evm_env);
426    ccx.ecx.set_tx(tx_env);
427    Ok(result.abi_encode())
428}
429
430fn check_broadcast(state: &Cheatcodes) -> Result<()> {
431    if state.broadcast.is_none() {
432        Ok(())
433    } else {
434        Err(fmt_err!("cannot select forks during a broadcast"))
435    }
436}
437
438fn transact<CTX: FoundryContextExt>(
439    ccx: &mut CheatsCtxt<'_, CTX>,
440    executor: &mut dyn CheatcodesExecutor<CTX>,
441    transaction: B256,
442    fork_id: Option<U256>,
443) -> Result {
444    executor.transact_on_db(ccx.state, ccx.ecx, fork_id, transaction)?;
445    Ok(Default::default())
446}
447
448// Helper to add the caller of fork cheat code as persistent account (in order to make sure that the
449// state of caller contract is not lost when fork changes).
450// Applies to create, select and roll forks actions.
451// https://github.com/foundry-rs/foundry/issues/8004
452fn persist_caller<CTX: ContextTr<Db: DatabaseExt>>(ccx: &mut CheatsCtxt<'_, CTX>) {
453    ccx.ecx.db_mut().add_persistent_account(ccx.caller);
454}
455
456/// Performs an Ethereum JSON-RPC request to the given endpoint.
457fn rpc_call(url: &str, method: &str, params: &str) -> Result {
458    let provider = ProviderBuilder::<AnyNetwork>::new(url).build()?;
459    let params_json: serde_json::Value = serde_json::from_str(params)?;
460    let result =
461        foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json))
462            .map_err(|err| fmt_err!("{method:?}: {err}"))?;
463    let result_as_tokens = convert_to_bytes(
464        &json_value_to_token(&result, None)
465            .map_err(|err| fmt_err!("failed to parse result: {err}"))?,
466    );
467
468    Ok(result_as_tokens.abi_encode())
469}
470
471/// Convert fixed bytes and address values to bytes in order to prevent encoding issues.
472fn convert_to_bytes(token: &DynSolValue) -> DynSolValue {
473    match token {
474        // Convert fixed bytes to prevent encoding issues.
475        // See: <https://github.com/foundry-rs/foundry/issues/8287>
476        DynSolValue::FixedBytes(bytes, size) => {
477            DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec())
478        }
479        DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()),
480        //  Convert tuple values to prevent encoding issues.
481        // See: <https://github.com/foundry-rs/foundry/issues/7858>
482        DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()),
483        val => val.clone(),
484    }
485}