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
276fn 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
285fn 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
292fn 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
310fn 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
321fn 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
372fn persist_caller(ccx: &mut CheatsCtxt) {
377 ccx.ecx.db.add_persistent_account(ccx.caller);
378}
379
380fn 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
394fn convert_to_bytes(token: &DynSolValue) -> DynSolValue {
396 match token {
397 DynSolValue::FixedBytes(bytes, size) => {
400 DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec())
401 }
402 DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()),
403 DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()),
406 val => val.clone(),
407 }
408}