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