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