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