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, AsEnvMut, ContextExt};
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 - 1")
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
269fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>) -> Result {
271 check_broadcast(ccx.state)?;
272
273 let fork = create_fork_request(ccx, url_or_alias, block)?;
274 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
275 let id = db.create_select_fork(fork, &mut env, journal)?;
276 Ok(id.abi_encode())
277}
278
279fn create_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>) -> Result {
281 let fork = create_fork_request(ccx, url_or_alias, block)?;
282 let id = ccx.ecx.journaled_state.database.create_fork(fork)?;
283 Ok(id.abi_encode())
284}
285
286fn create_select_fork_at_transaction(
288 ccx: &mut CheatsCtxt,
289 url_or_alias: &str,
290 transaction: &B256,
291) -> Result {
292 check_broadcast(ccx.state)?;
293
294 let fork = create_fork_request(ccx, url_or_alias, None)?;
295 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
296 let id = db.create_select_fork_at_transaction(fork, &mut env, journal, *transaction)?;
297 Ok(id.abi_encode())
298}
299
300fn create_fork_at_transaction(
302 ccx: &mut CheatsCtxt,
303 url_or_alias: &str,
304 transaction: &B256,
305) -> Result {
306 let fork = create_fork_request(ccx, url_or_alias, None)?;
307 let id = ccx.ecx.journaled_state.database.create_fork_at_transaction(fork, *transaction)?;
308 Ok(id.abi_encode())
309}
310
311fn create_fork_request(
313 ccx: &mut CheatsCtxt,
314 url_or_alias: &str,
315 block: Option<u64>,
316) -> Result<CreateFork> {
317 persist_caller(ccx);
318
319 let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?;
320 let url = rpc_endpoint.url()?;
321 let mut evm_opts = ccx.state.config.evm_opts.clone();
322 evm_opts.fork_block_number = block;
323 evm_opts.fork_retries = rpc_endpoint.config.retries;
324 evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff;
325 if let Some(Ok(auth)) = rpc_endpoint.auth {
326 evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]);
327 }
328 let fork = CreateFork {
329 enable_caching: !ccx.state.config.no_storage_caching &&
330 ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url),
331 url,
332 env: ccx.ecx.as_env_mut().to_owned(),
333 evm_opts,
334 };
335 Ok(fork)
336}
337
338fn check_broadcast(state: &Cheatcodes) -> Result<()> {
339 if state.broadcast.is_none() {
340 Ok(())
341 } else {
342 Err(fmt_err!("cannot select forks during a broadcast"))
343 }
344}
345
346fn transact(
347 ccx: &mut CheatsCtxt,
348 executor: &mut dyn CheatcodesExecutor,
349 transaction: B256,
350 fork_id: Option<U256>,
351) -> Result {
352 let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
353 db.transact(
354 fork_id,
355 transaction,
356 env.to_owned(),
357 journal,
358 &mut *executor.get_inspector(ccx.state),
359 )?;
360 Ok(Default::default())
361}
362
363fn persist_caller(ccx: &mut CheatsCtxt) {
368 ccx.ecx.journaled_state.database.add_persistent_account(ccx.caller);
369}
370
371fn rpc_call(url: &str, method: &str, params: &str) -> Result {
373 let provider = ProviderBuilder::new(url).build()?;
374 let params_json: serde_json::Value = serde_json::from_str(params)?;
375 let result =
376 foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json))
377 .map_err(|err| fmt_err!("{method:?}: {err}"))?;
378 let result_as_tokens = convert_to_bytes(
379 &json_value_to_token(&result).map_err(|err| fmt_err!("failed to parse result: {err}"))?,
380 );
381
382 Ok(result_as_tokens.abi_encode())
383}
384
385fn convert_to_bytes(token: &DynSolValue) -> DynSolValue {
387 match token {
388 DynSolValue::FixedBytes(bytes, size) => {
391 DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec())
392 }
393 DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()),
394 DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()),
397 val => val.clone(),
398 }
399}