Skip to main content

foundry_cheatcodes/evm/
fork.rs

1use crate::{
2    Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, 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::{Address, B256, U256, map::AddressHashMap};
9use alloy_provider::{Provider, RootProvider};
10use alloy_rpc_types::Filter;
11use alloy_sol_types::SolValue;
12use foundry_common::provider::ProviderBuilder;
13use foundry_evm_core::{
14    FoundryContextExt,
15    backend::{ContextUpdateFor, ForkAccountField, JournaledState, LocalForkId},
16    evm::{BlockEnvFor, EvmFactoryFor, FoundryContextFor, FoundryEvmNetwork, SpecFor, TxEnvFor},
17    fork::CreateFork,
18};
19use revm::context::ContextTr;
20
21impl Cheatcode for activeForkCall {
22    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
23        let Self {} = self;
24        ccx.active_fork_id().map(|id| id.abi_encode()).ok_or_else(|| fmt_err!("no active fork"))
25    }
26}
27
28impl Cheatcode for createFork_0Call {
29    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
30        let Self { urlOrAlias } = self;
31        create_fork(ccx, urlOrAlias, None)
32    }
33}
34
35impl Cheatcode for createFork_1Call {
36    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
37        let Self { urlOrAlias, blockNumber } = self;
38        create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
39    }
40}
41
42impl Cheatcode for createFork_2Call {
43    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
44        let Self { urlOrAlias, txHash } = self;
45        create_fork_at_transaction(ccx, urlOrAlias, txHash)
46    }
47}
48
49impl Cheatcode for createSelectFork_0Call {
50    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
51        let Self { urlOrAlias } = self;
52        create_select_fork(ccx, urlOrAlias, None)
53    }
54}
55
56impl Cheatcode for createSelectFork_1Call {
57    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
58        let Self { urlOrAlias, blockNumber } = self;
59        create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
60    }
61}
62
63impl Cheatcode for createSelectFork_2Call {
64    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
65        let Self { urlOrAlias, txHash } = self;
66        create_select_fork_at_transaction(ccx, urlOrAlias, txHash)
67    }
68}
69
70impl Cheatcode for rollFork_0Call {
71    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
72        let Self { blockNumber } = self;
73        persist_caller(ccx);
74        let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
75            db.roll_fork(None, (*blockNumber).to(), evm_env, tx_env, inner)
76                .map(|context| ((), context))
77        })?;
78        record_fork_roll(ccx, None);
79        Ok(result)
80    }
81}
82
83impl Cheatcode for rollFork_1Call {
84    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
85        let Self { txHash } = self;
86        persist_caller(ccx);
87        let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
88            db.roll_fork_to_transaction(None, *txHash, evm_env, tx_env, inner)
89                .map(|context| ((), context))
90        })?;
91        record_fork_roll(ccx, None);
92        Ok(result)
93    }
94}
95
96impl Cheatcode for rollFork_2Call {
97    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
98        let Self { forkId, blockNumber } = self;
99        persist_caller(ccx);
100        let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
101            db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, tx_env, inner)
102                .map(|context| ((), context))
103        })?;
104        record_fork_roll(ccx, Some(*forkId));
105        Ok(result)
106    }
107}
108
109impl Cheatcode for rollFork_3Call {
110    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
111        let Self { forkId, txHash } = self;
112        persist_caller(ccx);
113        let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
114            db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, tx_env, inner)
115                .map(|context| ((), context))
116        })?;
117        record_fork_roll(ccx, Some(*forkId));
118        Ok(result)
119    }
120}
121
122impl Cheatcode for selectForkCall {
123    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
124        let Self { forkId } = self;
125        persist_caller(ccx);
126        check_broadcast(ccx.state)?;
127        let source_fork_id = ccx.active_fork_id();
128        let initial = ccx.state.created_account_bindings(None);
129        let propagated = persistent_created_accounts(ccx);
130        let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
131            db.select_fork(*forkId, evm_env, tx_env, inner).map(|context| ((), context))
132        })?;
133        record_fork_switch(ccx, source_fork_id, initial, propagated);
134        Ok(result)
135    }
136}
137
138impl Cheatcode for transact_0Call {
139    fn apply_full<FEN: FoundryEvmNetwork>(
140        &self,
141        ccx: &mut CheatsCtxt<'_, '_, FEN>,
142        executor: &mut dyn CheatcodesExecutor<FEN>,
143    ) -> Result {
144        let Self { txHash } = *self;
145        transact(ccx, executor, txHash, None)
146    }
147}
148
149impl Cheatcode for transact_1Call {
150    fn apply_full<FEN: FoundryEvmNetwork>(
151        &self,
152        ccx: &mut CheatsCtxt<'_, '_, FEN>,
153        executor: &mut dyn CheatcodesExecutor<FEN>,
154    ) -> Result {
155        let Self { forkId, txHash } = *self;
156        transact(ccx, executor, txHash, Some(forkId))
157    }
158}
159
160impl Cheatcode for allowCheatcodesCall {
161    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
162        let Self { account } = self;
163        ccx.ecx.db_mut().allow_cheatcode_access(*account);
164        Ok(Default::default())
165    }
166}
167
168impl Cheatcode for makePersistent_0Call {
169    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
170        let Self { account } = self;
171        ccx.ecx.db_mut().add_persistent_account(*account);
172        Ok(Default::default())
173    }
174}
175
176impl Cheatcode for makePersistent_1Call {
177    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
178        let Self { account0, account1 } = self;
179        ccx.ecx.db_mut().add_persistent_account(*account0);
180        ccx.ecx.db_mut().add_persistent_account(*account1);
181        Ok(Default::default())
182    }
183}
184
185impl Cheatcode for makePersistent_2Call {
186    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
187        let Self { account0, account1, account2 } = self;
188        ccx.ecx.db_mut().add_persistent_account(*account0);
189        ccx.ecx.db_mut().add_persistent_account(*account1);
190        ccx.ecx.db_mut().add_persistent_account(*account2);
191        Ok(Default::default())
192    }
193}
194
195impl Cheatcode for makePersistent_3Call {
196    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> 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<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
207        let Self { account } = self;
208        ccx.ecx.db_mut().remove_persistent_account(account);
209        Ok(Default::default())
210    }
211}
212
213impl Cheatcode for revokePersistent_1Call {
214    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
215        let Self { accounts } = self;
216        for account in accounts {
217            ccx.ecx.db_mut().remove_persistent_account(account);
218        }
219        Ok(Default::default())
220    }
221}
222
223impl Cheatcode for isPersistentCall {
224    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
225        let Self { account } = self;
226        Ok(ccx.is_persistent(account).abi_encode())
227    }
228}
229
230impl Cheatcode for rpc_0Call {
231    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
232        let Self { method, params } = self;
233        let url = ccx.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
234        let result = rpc_call(&url, method, params)?;
235        refresh_active_fork_state(ccx, method, params)?;
236        Ok(result)
237    }
238}
239
240impl Cheatcode for rpc_1Call {
241    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
242        let Self { urlOrAlias, method, params } = self;
243        let url = state.config.rpc_endpoint(urlOrAlias)?.url()?;
244        rpc_call(&url, method, params)
245    }
246}
247
248impl Cheatcode for rpcJson_0Call {
249    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
250        let Self { method, params } = self;
251        let url = ccx.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
252        let result = rpc_json_call(&url, method, params)?;
253        refresh_active_fork_state(ccx, method, params)?;
254        Ok(result)
255    }
256}
257
258impl Cheatcode for rpcJson_1Call {
259    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
260        let Self { urlOrAlias, method, params } = self;
261        let url = state.config.rpc_endpoint(urlOrAlias)?.url()?;
262        rpc_json_call(&url, method, params)
263    }
264}
265
266impl Cheatcode for eth_getLogsCall {
267    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
268        let Self { fromBlock, toBlock, target, topics } = self;
269        let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock))
270        else {
271            bail!("blocks in block range must be less than 2^64")
272        };
273
274        if topics.len() > 4 {
275            bail!("topics array must contain at most 4 elements")
276        }
277
278        let provider = active_fork_provider(ccx)?;
279        let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block);
280        for (i, &topic) in topics.iter().enumerate() {
281            filter.topics[i] = topic.into();
282        }
283
284        let logs = foundry_common::block_on(provider.get_logs(&filter))
285            .map_err(|e| fmt_err!("failed to get logs: {e}"))?;
286
287        let eth_logs = logs
288            .into_iter()
289            .map(|log| EthGetLogs {
290                emitter: log.address(),
291                topics: log.topics().to_vec(),
292                data: log.inner.data.data,
293                blockHash: log.block_hash.unwrap_or_default(),
294                blockNumber: log.block_number.unwrap_or_default(),
295                transactionHash: log.transaction_hash.unwrap_or_default(),
296                transactionIndex: log.transaction_index.unwrap_or_default(),
297                logIndex: U256::from(log.log_index.unwrap_or_default()),
298                removed: log.removed,
299            })
300            .collect::<Vec<_>>();
301
302        Ok(eth_logs.abi_encode())
303    }
304}
305
306impl Cheatcode for eth_getProofCall {
307    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
308        let Self { target, slots, blockNumber } = self;
309        let block_number = u64::try_from(blockNumber)
310            .map_err(|_| fmt_err!("block number must be less than 2^64"))?;
311
312        let provider = active_fork_provider(ccx)?;
313        let proof = foundry_common::block_on(async move {
314            provider.get_proof(*target, slots.clone()).number(block_number).await
315        })
316        .map_err(|e| fmt_err!("failed to get proof: {e}"))?;
317
318        let storage_proof = proof
319            .storage_proof
320            .into_iter()
321            .map(|p| EthStorageProof { key: p.key.as_b256(), value: p.value, proof: p.proof })
322            .collect();
323
324        Ok(EthGetProof {
325            account: proof.address,
326            balance: proof.balance,
327            codeHash: proof.code_hash,
328            nonce: proof.nonce,
329            storageHash: proof.storage_hash,
330            accountProof: proof.account_proof,
331            storageProof: storage_proof,
332        }
333        .abi_encode())
334    }
335}
336
337impl Cheatcode for getRawBlockHeaderCall {
338    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
339        let Self { blockNumber } = self;
340        let provider = active_fork_provider(ccx)?;
341        let block_number = u64::try_from(blockNumber)
342            .map_err(|_| fmt_err!("block number must be less than 2^64"))?;
343        let block =
344            foundry_common::block_on(async move { provider.get_block(block_number.into()).await })
345                .map_err(|e| fmt_err!("failed to get block: {e}"))?
346                .ok_or_else(|| fmt_err!("block {block_number} not found"))?;
347
348        let header: alloy_consensus::Header = block
349            .into_inner()
350            .header
351            .inner
352            .try_into_header()
353            .map_err(|e| fmt_err!("failed to convert to header: {e}"))?;
354        Ok(alloy_rlp::encode(&header).abi_encode())
355    }
356}
357
358/// Creates and then also selects the new fork
359fn create_select_fork<FEN: FoundryEvmNetwork>(
360    ccx: &mut CheatsCtxt<'_, '_, FEN>,
361    url_or_alias: &str,
362    block: Option<u64>,
363) -> Result {
364    check_broadcast(ccx.state)?;
365
366    let fork = create_fork_request(ccx, url_or_alias, block)?;
367    let source_fork_id = ccx.active_fork_id();
368    let initial = ccx.state.created_account_bindings(None);
369    let propagated = persistent_created_accounts(ccx);
370    let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
371        db.create_select_fork(fork, evm_env, tx_env, inner)
372    })?;
373    record_fork_switch(ccx, source_fork_id, initial, propagated);
374    Ok(result)
375}
376
377/// Creates a new fork
378fn create_fork<FEN: FoundryEvmNetwork>(
379    ccx: &mut CheatsCtxt<'_, '_, FEN>,
380    url_or_alias: &str,
381    block: Option<u64>,
382) -> Result {
383    let fork = create_fork_request(ccx, url_or_alias, block)?;
384    let id = ccx.ecx.db_mut().create_fork(fork)?;
385    Ok(id.abi_encode())
386}
387
388/// Creates and then also selects the new fork at the given transaction
389fn create_select_fork_at_transaction<FEN: FoundryEvmNetwork>(
390    ccx: &mut CheatsCtxt<'_, '_, FEN>,
391    url_or_alias: &str,
392    transaction: &B256,
393) -> Result {
394    check_broadcast(ccx.state)?;
395
396    let fork = create_fork_request(ccx, url_or_alias, None)?;
397    let source_fork_id = ccx.active_fork_id();
398    let initial = ccx.state.created_account_bindings(None);
399    let propagated = persistent_created_accounts(ccx);
400    let result = fork_env_op::<FEN, _>(ccx.ecx, |db, evm_env, tx_env, inner| {
401        db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction)
402    })?;
403    record_fork_switch(ccx, source_fork_id, initial, propagated);
404    Ok(result)
405}
406
407/// Creates a new fork at the given transaction
408fn create_fork_at_transaction<FEN: FoundryEvmNetwork>(
409    ccx: &mut CheatsCtxt<'_, '_, FEN>,
410    url_or_alias: &str,
411    transaction: &B256,
412) -> Result {
413    let fork = create_fork_request(ccx, url_or_alias, None)?;
414    let id = ccx.ecx.db_mut().create_fork_at_transaction(fork, *transaction)?;
415    Ok(id.abi_encode())
416}
417
418/// Creates the request object for a new fork request
419fn create_fork_request<FEN: FoundryEvmNetwork>(
420    ccx: &mut CheatsCtxt<'_, '_, FEN>,
421    url_or_alias: &str,
422    block: Option<u64>,
423) -> Result<CreateFork> {
424    persist_caller(ccx);
425
426    let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?;
427    let url = rpc_endpoint.url()?;
428    let mut evm_opts = ccx.state.config.evm_opts.clone();
429    evm_opts.fork_block_number = block;
430    evm_opts.fork_block_number_is_inferred = false;
431    evm_opts.fork_retries = rpc_endpoint.config.retries;
432    evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff;
433    if let Some(Ok(auth)) = rpc_endpoint.auth {
434        let fork_headers = Some(vec![format!("Authorization: {auth}")]);
435        // The cached identity was discovered with other credentials, which may reach another
436        // backend behind the same URL.
437        if evm_opts.fork_headers != fork_headers {
438            evm_opts.fork_endpoint = None;
439        }
440        evm_opts.fork_headers = fork_headers;
441    }
442    let fork = CreateFork {
443        enable_caching: !ccx.state.config.no_storage_caching
444            && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url),
445        url,
446        evm_opts,
447    };
448    Ok(fork)
449}
450
451/// Applies a fork/roll/transact context update to the active EVM context.
452#[cfg(feature = "monad")]
453fn apply_context_update<FEN: FoundryEvmNetwork>(
454    ecx: &mut FoundryContextFor<'_, FEN>,
455    context_update: foundry_evm_core::backend::ContextUpdate<foundry_evm_core::evm::ChainFor<FEN>>,
456) {
457    match context_update {
458        foundry_evm_core::backend::ContextUpdate::Unchanged => {}
459        foundry_evm_core::backend::ContextUpdate::Replace(chain_context) => {
460            *ecx.chain_mut() = chain_context;
461            foundry_evm_core::evm::refresh_chain_journal(ecx);
462        }
463        foundry_evm_core::backend::ContextUpdate::Rebase => {
464            foundry_evm_core::evm::refresh_chain_journal(ecx);
465        }
466    }
467}
468
469/// Clones the EVM and tx environments, runs a fork operation that may modify them, then writes
470/// them back. This is the common pattern for all fork-switching cheatcodes (rollFork, selectFork,
471/// createSelectFork).
472fn fork_env_op<FEN: FoundryEvmNetwork, T: SolValue>(
473    ecx: &mut FoundryContextFor<'_, FEN>,
474    f: impl FnOnce(
475        &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
476        &mut EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
477        &mut TxEnvFor<FEN>,
478        &mut JournaledState,
479    ) -> eyre::Result<(T, ContextUpdateFor<EvmFactoryFor<FEN>>)>,
480) -> Result {
481    let mut evm_env = ecx.evm_clone();
482    let mut tx_env = ecx.tx_clone();
483    let (db, inner) = ecx.db_journal_inner_mut();
484    let (result, context_update) = f(db, &mut evm_env, &mut tx_env, inner)?;
485    ecx.set_evm(evm_env);
486    ecx.set_tx(tx_env);
487    #[cfg(not(feature = "monad"))]
488    let _ = context_update;
489    #[cfg(feature = "monad")]
490    apply_context_update::<FEN>(ecx, context_update);
491    Ok(result.abi_encode())
492}
493
494fn persistent_created_accounts<FEN: FoundryEvmNetwork>(
495    ccx: &CheatsCtxt<'_, '_, FEN>,
496) -> Vec<(Address, usize)> {
497    let fork_id = ccx.active_fork_id();
498    ccx.state
499        .created_account_bindings(fork_id)
500        .into_iter()
501        .filter(|(address, _)| ccx.is_persistent(address))
502        .collect()
503}
504
505fn record_fork_switch<FEN: FoundryEvmNetwork>(
506    ccx: &mut CheatsCtxt<'_, '_, FEN>,
507    source_fork_id: Option<LocalForkId>,
508    initial: AddressHashMap<usize>,
509    propagated: Vec<(Address, usize)>,
510) {
511    let target_fork_id = ccx.active_fork_id();
512    ccx.state.fork_block_number_override = ccx.active_fork_block_number();
513    if source_fork_id != target_fork_id {
514        ccx.state.commit_created_account_changes(source_fork_id);
515    }
516    ccx.state.record_initial_created_accounts(target_fork_id, initial);
517    ccx.state.record_propagated_accounts(target_fork_id, propagated);
518}
519
520fn record_fork_roll<FEN: FoundryEvmNetwork>(
521    ccx: &mut CheatsCtxt<'_, '_, FEN>,
522    target_fork_id: Option<LocalForkId>,
523) {
524    let active_fork_id = ccx.active_fork_id();
525    let rolled_fork_id = target_fork_id.or(active_fork_id);
526    ccx.state.env_overrides.clear_implicit_basefee(rolled_fork_id);
527    if target_fork_id.is_none() || target_fork_id == active_fork_id {
528        ccx.state.fork_block_number_override = ccx.active_fork_block_number();
529        ccx.state.commit_created_account_changes(active_fork_id);
530    }
531}
532
533fn check_broadcast<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>) -> Result<()> {
534    if state.broadcast.is_none() {
535        Ok(())
536    } else {
537        Err(fmt_err!("cannot select forks during a broadcast"))
538    }
539}
540
541fn transact<FEN: FoundryEvmNetwork>(
542    ccx: &mut CheatsCtxt<'_, '_, FEN>,
543    executor: &mut dyn CheatcodesExecutor<FEN>,
544    transaction: B256,
545    fork_id: Option<U256>,
546) -> Result {
547    let context_update = executor.transact_on_db(ccx.state, ccx.ecx, fork_id, transaction)?;
548    #[cfg(not(feature = "monad"))]
549    let _ = context_update;
550    #[cfg(feature = "monad")]
551    apply_context_update::<FEN>(ccx.ecx, context_update);
552    Ok(Default::default())
553}
554
555// Helper to add the caller of fork cheat code as persistent account (in order to make sure that the
556// state of caller contract is not lost when fork changes).
557// Applies to create, select and roll forks actions.
558// https://github.com/foundry-rs/foundry/issues/8004
559fn persist_caller<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) {
560    ccx.ecx.db_mut().add_persistent_account(ccx.caller);
561}
562
563/// Returns a provider for the active fork, configured with the RPC options the fork was created
564/// with (e.g. auth headers, retries and timeouts).
565fn active_fork_provider<FEN: FoundryEvmNetwork>(
566    ccx: &CheatsCtxt<'_, '_, FEN>,
567) -> Result<RootProvider<AnyNetwork>> {
568    let fork = ccx.active_fork_options().ok_or_else(|| fmt_err!("no active fork URL found"))?;
569    Ok(fork.evm_opts.fork_provider_with_url(&fork.url)?)
570}
571
572/// Performs an Ethereum JSON-RPC request to the given endpoint.
573fn rpc_call(url: &str, method: &str, params: &str) -> Result {
574    let result = rpc_result(url, method, params)?;
575    let result_as_tokens = convert_to_bytes(
576        &json_value_to_token(&result, None)
577            .map_err(|err| fmt_err!("failed to parse result: {err}"))?,
578    );
579
580    let payload = match &result_as_tokens {
581        DynSolValue::Bytes(b) => b.clone(),
582        _ => result_as_tokens.abi_encode(),
583    };
584    Ok(DynSolValue::Bytes(payload).abi_encode())
585}
586
587/// Performs an Ethereum JSON-RPC request to the given endpoint and returns the JSON result.
588fn rpc_json_call(url: &str, method: &str, params: &str) -> Result {
589    let result = rpc_result(url, method, params)?;
590    Ok(serde_json::to_string(&result)?.abi_encode())
591}
592
593/// Refreshes cached fork and journal state after a `vm.rpc` call on the active fork that mutates
594/// the node directly. Only well-known Anvil/Hardhat account/storage setters are handled;
595/// chain-advancing methods (e.g. `eth_sendTransaction`) need re-forking, not synchronization.
596fn refresh_active_fork_state<FEN: FoundryEvmNetwork>(
597    ccx: &mut CheatsCtxt<'_, '_, FEN>,
598    method: &str,
599    params: &str,
600) -> Result<()> {
601    let account_field = match method {
602        "anvil_setBalance"
603        | "hardhat_setBalance"
604        | "tenderly_setBalance"
605        | "anvil_addBalance"
606        | "hardhat_addBalance"
607        | "tenderly_addBalance" => Some(ForkAccountField::Balance),
608        "anvil_setNonce" | "hardhat_setNonce" | "evm_setAccountNonce" => {
609            Some(ForkAccountField::Nonce)
610        }
611        "anvil_setCode" | "hardhat_setCode" => Some(ForkAccountField::Code),
612        "anvil_setStorageAt" | "hardhat_setStorageAt" => None,
613        _ => return Ok(()),
614    };
615
616    let Ok(params) = serde_json::from_str::<serde_json::Value>(params) else { return Ok(()) };
617    let Some(address) =
618        params.get(0).and_then(|v| v.as_str()).and_then(|s| s.parse::<Address>().ok())
619    else {
620        return Ok(());
621    };
622
623    let (db, journaled_state) = ccx.ecx.db_journal_inner_mut();
624    if let Some(field) = account_field {
625        db.refresh_fork_account(address, field, journaled_state)?;
626    } else {
627        let Some(slot) =
628            params.get(1).and_then(|v| v.as_str()).and_then(|s| s.parse::<U256>().ok())
629        else {
630            return Ok(());
631        };
632        db.refresh_fork_storage(address, slot, journaled_state)?;
633    }
634    Ok(())
635}
636
637fn rpc_result(url: &str, method: &str, params: &str) -> Result<serde_json::Value> {
638    let provider = ProviderBuilder::<AnyNetwork>::new(url).build()?;
639    let params_json: serde_json::Value = serde_json::from_str(params)?;
640    foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json))
641        .map_err(|err| fmt_err!("{method:?}: {err}"))
642}
643
644/// Convert fixed bytes and address values to bytes in order to prevent encoding issues.
645fn convert_to_bytes(token: &DynSolValue) -> DynSolValue {
646    match token {
647        // Convert fixed bytes to prevent encoding issues.
648        // See: <https://github.com/foundry-rs/foundry/issues/8287>
649        DynSolValue::FixedBytes(bytes, size) => {
650            DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec())
651        }
652        DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()),
653        val => val.clone(),
654    }
655}