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