foundry_cheatcodes/evm/
fork.rs

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