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};
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, backend::JournaledState, evm::FoundryEvmNetwork, fork::CreateFork,
15};
16use revm::context::ContextTr;
17
18impl Cheatcode for activeForkCall {
19 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
20 let Self {} = self;
21 ccx.ecx
22 .db()
23 .active_fork_id()
24 .map(|id| id.abi_encode())
25 .ok_or_else(|| fmt_err!("no active fork"))
26 }
27}
28
29impl Cheatcode for createFork_0Call {
30 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
31 let Self { urlOrAlias } = self;
32 create_fork(ccx, urlOrAlias, None)
33 }
34}
35
36impl Cheatcode for createFork_1Call {
37 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
38 let Self { urlOrAlias, blockNumber } = self;
39 create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
40 }
41}
42
43impl Cheatcode for createFork_2Call {
44 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
45 let Self { urlOrAlias, txHash } = self;
46 create_fork_at_transaction(ccx, urlOrAlias, txHash)
47 }
48}
49
50impl Cheatcode for createSelectFork_0Call {
51 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
52 let Self { urlOrAlias } = self;
53 create_select_fork(ccx, urlOrAlias, None)
54 }
55}
56
57impl Cheatcode for createSelectFork_1Call {
58 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
59 let Self { urlOrAlias, blockNumber } = self;
60 create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
61 }
62}
63
64impl Cheatcode for createSelectFork_2Call {
65 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
66 let Self { urlOrAlias, txHash } = self;
67 create_select_fork_at_transaction(ccx, urlOrAlias, txHash)
68 }
69}
70
71impl Cheatcode for rollFork_0Call {
72 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
73 let Self { blockNumber } = self;
74 persist_caller(ccx);
75 fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
76 db.roll_fork(None, (*blockNumber).to(), evm_env, inner)
77 })
78 }
79}
80
81impl Cheatcode for rollFork_1Call {
82 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
83 let Self { txHash } = self;
84 persist_caller(ccx);
85 fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
86 db.roll_fork_to_transaction(None, *txHash, evm_env, inner)
87 })
88 }
89}
90
91impl Cheatcode for rollFork_2Call {
92 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
93 let Self { forkId, blockNumber } = self;
94 persist_caller(ccx);
95 fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
96 db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, inner)
97 })
98 }
99}
100
101impl Cheatcode for rollFork_3Call {
102 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
103 let Self { forkId, txHash } = self;
104 persist_caller(ccx);
105 fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
106 db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, inner)
107 })
108 }
109}
110
111impl Cheatcode for selectForkCall {
112 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
113 let Self { forkId } = self;
114 persist_caller(ccx);
115 check_broadcast(ccx.state)?;
116 fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
117 db.select_fork(*forkId, evm_env, tx_env, inner)
118 })
119 }
120}
121
122impl Cheatcode for transact_0Call {
123 fn apply_full<FEN: FoundryEvmNetwork>(
124 &self,
125 ccx: &mut CheatsCtxt<'_, '_, FEN>,
126 executor: &mut dyn CheatcodesExecutor<FEN>,
127 ) -> Result {
128 let Self { txHash } = *self;
129 transact(ccx, executor, txHash, None)
130 }
131}
132
133impl Cheatcode for transact_1Call {
134 fn apply_full<FEN: FoundryEvmNetwork>(
135 &self,
136 ccx: &mut CheatsCtxt<'_, '_, FEN>,
137 executor: &mut dyn CheatcodesExecutor<FEN>,
138 ) -> Result {
139 let Self { forkId, txHash } = *self;
140 transact(ccx, executor, txHash, Some(forkId))
141 }
142}
143
144impl Cheatcode for allowCheatcodesCall {
145 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
146 let Self { account } = self;
147 ccx.ecx.db_mut().allow_cheatcode_access(*account);
148 Ok(Default::default())
149 }
150}
151
152impl Cheatcode for makePersistent_0Call {
153 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
154 let Self { account } = self;
155 ccx.ecx.db_mut().add_persistent_account(*account);
156 Ok(Default::default())
157 }
158}
159
160impl Cheatcode for makePersistent_1Call {
161 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
162 let Self { account0, account1 } = self;
163 ccx.ecx.db_mut().add_persistent_account(*account0);
164 ccx.ecx.db_mut().add_persistent_account(*account1);
165 Ok(Default::default())
166 }
167}
168
169impl Cheatcode for makePersistent_2Call {
170 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
171 let Self { account0, account1, account2 } = self;
172 ccx.ecx.db_mut().add_persistent_account(*account0);
173 ccx.ecx.db_mut().add_persistent_account(*account1);
174 ccx.ecx.db_mut().add_persistent_account(*account2);
175 Ok(Default::default())
176 }
177}
178
179impl Cheatcode for makePersistent_3Call {
180 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
181 let Self { accounts } = self;
182 for account in accounts {
183 ccx.ecx.db_mut().add_persistent_account(*account);
184 }
185 Ok(Default::default())
186 }
187}
188
189impl Cheatcode for revokePersistent_0Call {
190 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
191 let Self { account } = self;
192 ccx.ecx.db_mut().remove_persistent_account(account);
193 Ok(Default::default())
194 }
195}
196
197impl Cheatcode for revokePersistent_1Call {
198 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
199 let Self { accounts } = self;
200 for account in accounts {
201 ccx.ecx.db_mut().remove_persistent_account(account);
202 }
203 Ok(Default::default())
204 }
205}
206
207impl Cheatcode for isPersistentCall {
208 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
209 let Self { account } = self;
210 Ok(ccx.ecx.db().is_persistent(account).abi_encode())
211 }
212}
213
214impl Cheatcode for rpc_0Call {
215 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
216 let Self { method, params } = self;
217 let url =
218 ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
219 let result = rpc_call(&url, method, params)?;
220 invalidate_active_fork_cache(ccx, method, params);
221 Ok(result)
222 }
223}
224
225impl Cheatcode for rpc_1Call {
226 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
227 let Self { urlOrAlias, method, params } = self;
228 let url = state.config.rpc_endpoint(urlOrAlias)?.url()?;
229 rpc_call(&url, method, params)
230 }
231}
232
233impl Cheatcode for rpcJson_0Call {
234 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
235 let Self { method, params } = self;
236 let url =
237 ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
238 let result = rpc_json_call(&url, method, params)?;
239 invalidate_active_fork_cache(ccx, method, params);
240 Ok(result)
241 }
242}
243
244impl Cheatcode for rpcJson_1Call {
245 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
246 let Self { urlOrAlias, method, params } = self;
247 let url = state.config.rpc_endpoint(urlOrAlias)?.url()?;
248 rpc_json_call(&url, method, params)
249 }
250}
251
252impl Cheatcode for eth_getLogsCall {
253 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
254 let Self { fromBlock, toBlock, target, topics } = self;
255 let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock))
256 else {
257 bail!("blocks in block range must be less than 2^64")
258 };
259
260 if topics.len() > 4 {
261 bail!("topics array must contain at most 4 elements")
262 }
263
264 let url =
265 ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
266 let provider = ProviderBuilder::<AnyNetwork>::new(&url).build()?;
267 let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block);
268 for (i, &topic) in topics.iter().enumerate() {
269 filter.topics[i] = topic.into();
270 }
271
272 let logs = foundry_common::block_on(provider.get_logs(&filter))
273 .map_err(|e| fmt_err!("failed to get logs: {e}"))?;
274
275 let eth_logs = logs
276 .into_iter()
277 .map(|log| EthGetLogs {
278 emitter: log.address(),
279 topics: log.topics().to_vec(),
280 data: log.inner.data.data,
281 blockHash: log.block_hash.unwrap_or_default(),
282 blockNumber: log.block_number.unwrap_or_default(),
283 transactionHash: log.transaction_hash.unwrap_or_default(),
284 transactionIndex: log.transaction_index.unwrap_or_default(),
285 logIndex: U256::from(log.log_index.unwrap_or_default()),
286 removed: log.removed,
287 })
288 .collect::<Vec<_>>();
289
290 Ok(eth_logs.abi_encode())
291 }
292}
293
294impl Cheatcode for getRawBlockHeaderCall {
295 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
296 let Self { blockNumber } = self;
297 let url = ccx.ecx.db().active_fork_url().ok_or_else(|| fmt_err!("no active fork"))?;
298 let provider = ProviderBuilder::<AnyNetwork>::new(&url).build()?;
299 let block_number = u64::try_from(blockNumber)
300 .map_err(|_| fmt_err!("block number must be less than 2^64"))?;
301 let block =
302 foundry_common::block_on(async move { provider.get_block(block_number.into()).await })
303 .map_err(|e| fmt_err!("failed to get block: {e}"))?
304 .ok_or_else(|| fmt_err!("block {block_number} not found"))?;
305
306 let header: alloy_consensus::Header = block
307 .into_inner()
308 .header
309 .inner
310 .try_into_header()
311 .map_err(|e| fmt_err!("failed to convert to header: {e}"))?;
312 Ok(alloy_rlp::encode(&header).abi_encode())
313 }
314}
315
316fn create_select_fork<FEN: FoundryEvmNetwork>(
318 ccx: &mut CheatsCtxt<'_, '_, FEN>,
319 url_or_alias: &str,
320 block: Option<u64>,
321) -> Result {
322 check_broadcast(ccx.state)?;
323
324 let fork = create_fork_request(ccx, url_or_alias, block)?;
325 fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
326 db.create_select_fork(fork, evm_env, tx_env, inner)
327 })
328}
329
330fn create_fork<FEN: FoundryEvmNetwork>(
332 ccx: &mut CheatsCtxt<'_, '_, FEN>,
333 url_or_alias: &str,
334 block: Option<u64>,
335) -> Result {
336 let fork = create_fork_request(ccx, url_or_alias, block)?;
337 let id = ccx.ecx.db_mut().create_fork(fork)?;
338 Ok(id.abi_encode())
339}
340
341fn create_select_fork_at_transaction<FEN: FoundryEvmNetwork>(
343 ccx: &mut CheatsCtxt<'_, '_, FEN>,
344 url_or_alias: &str,
345 transaction: &B256,
346) -> Result {
347 check_broadcast(ccx.state)?;
348
349 let fork = create_fork_request(ccx, url_or_alias, None)?;
350 fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
351 db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction)
352 })
353}
354
355fn create_fork_at_transaction<FEN: FoundryEvmNetwork>(
357 ccx: &mut CheatsCtxt<'_, '_, FEN>,
358 url_or_alias: &str,
359 transaction: &B256,
360) -> Result {
361 let fork = create_fork_request(ccx, url_or_alias, None)?;
362 let id = ccx.ecx.db_mut().create_fork_at_transaction(fork, *transaction)?;
363 Ok(id.abi_encode())
364}
365
366fn create_fork_request<FEN: FoundryEvmNetwork>(
368 ccx: &mut CheatsCtxt<'_, '_, FEN>,
369 url_or_alias: &str,
370 block: Option<u64>,
371) -> Result<CreateFork> {
372 persist_caller(ccx);
373
374 let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?;
375 let url = rpc_endpoint.url()?;
376 let mut evm_opts = ccx.state.config.evm_opts.clone();
377 evm_opts.fork_block_number = block;
378 evm_opts.fork_retries = rpc_endpoint.config.retries;
379 evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff;
380 if let Some(Ok(auth)) = rpc_endpoint.auth {
381 evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]);
382 }
383 let fork = CreateFork {
384 enable_caching: !ccx.state.config.no_storage_caching
385 && ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url),
386 url,
387 evm_opts,
388 };
389 Ok(fork)
390}
391
392fn fork_env_op<CTX: FoundryContextExt, T: SolValue>(
396 ecx: &mut CTX,
397 f: impl FnOnce(
398 &mut CTX::Db,
399 &mut EvmEnv<CTX::Spec, CTX::Block>,
400 &mut CTX::Tx,
401 &mut JournaledState,
402 ) -> eyre::Result<T>,
403) -> Result {
404 let mut evm_env = ecx.evm_clone();
405 let mut tx_env = ecx.tx_clone();
406 let (db, inner) = ecx.db_journal_inner_mut();
407 let result = f(db, &mut evm_env, &mut tx_env, inner)?;
408 ecx.set_evm(evm_env);
409 ecx.set_tx(tx_env);
410 Ok(result.abi_encode())
411}
412
413fn check_broadcast<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>) -> Result<()> {
414 if state.broadcast.is_none() {
415 Ok(())
416 } else {
417 Err(fmt_err!("cannot select forks during a broadcast"))
418 }
419}
420
421fn transact<FEN: FoundryEvmNetwork>(
422 ccx: &mut CheatsCtxt<'_, '_, FEN>,
423 executor: &mut dyn CheatcodesExecutor<FEN>,
424 transaction: B256,
425 fork_id: Option<U256>,
426) -> Result {
427 executor.transact_on_db(ccx.state, ccx.ecx, fork_id, transaction)?;
428 Ok(Default::default())
429}
430
431fn persist_caller<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) {
436 ccx.ecx.db_mut().add_persistent_account(ccx.caller);
437}
438
439fn rpc_call(url: &str, method: &str, params: &str) -> Result {
441 let result = rpc_result(url, method, params)?;
442 let result_as_tokens = convert_to_bytes(
443 &json_value_to_token(&result, None)
444 .map_err(|err| fmt_err!("failed to parse result: {err}"))?,
445 );
446
447 let payload = match &result_as_tokens {
448 DynSolValue::Bytes(b) => b.clone(),
449 _ => result_as_tokens.abi_encode(),
450 };
451 Ok(DynSolValue::Bytes(payload).abi_encode())
452}
453
454fn rpc_json_call(url: &str, method: &str, params: &str) -> Result {
456 let result = rpc_result(url, method, params)?;
457 Ok(serde_json::to_string(&result)?.abi_encode())
458}
459
460fn invalidate_active_fork_cache<FEN: FoundryEvmNetwork>(
465 ccx: &mut CheatsCtxt<'_, '_, FEN>,
466 method: &str,
467 params: &str,
468) {
469 const ACCOUNT_SETTERS: &[&str] = &[
470 "anvil_setBalance",
471 "hardhat_setBalance",
472 "tenderly_setBalance",
473 "anvil_addBalance",
474 "hardhat_addBalance",
475 "tenderly_addBalance",
476 "anvil_setNonce",
477 "hardhat_setNonce",
478 "evm_setAccountNonce",
479 "anvil_setCode",
480 "hardhat_setCode",
481 ];
482 let is_storage_setter = matches!(method, "anvil_setStorageAt" | "hardhat_setStorageAt");
483 if !is_storage_setter && !ACCOUNT_SETTERS.contains(&method) {
484 return;
485 }
486
487 let Ok(params) = serde_json::from_str::<serde_json::Value>(params) else { return };
488 let Some(address) =
489 params.get(0).and_then(|v| v.as_str()).and_then(|s| s.parse::<Address>().ok())
490 else {
491 return;
492 };
493
494 if is_storage_setter {
495 let Some(slot) =
496 params.get(1).and_then(|v| v.as_str()).and_then(|s| s.parse::<U256>().ok())
497 else {
498 return;
499 };
500 ccx.ecx.db_mut().invalidate_fork_cache_storage(address, slot);
501 } else {
502 ccx.ecx.db_mut().invalidate_fork_cache_account(address);
503 }
504}
505
506fn rpc_result(url: &str, method: &str, params: &str) -> Result<serde_json::Value> {
507 let provider = ProviderBuilder::<AnyNetwork>::new(url).build()?;
508 let params_json: serde_json::Value = serde_json::from_str(params)?;
509 foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json))
510 .map_err(|err| fmt_err!("{method:?}: {err}"))
511}
512
513fn convert_to_bytes(token: &DynSolValue) -> DynSolValue {
515 match token {
516 DynSolValue::FixedBytes(bytes, size) => {
519 DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec())
520 }
521 DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()),
522 val => val.clone(),
523 }
524}