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