1use crate::{
4 BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result,
5 Vm::*,
6 inspector::{Ecx, RecordDebugStepInfo},
7};
8use alloy_consensus::TxEnvelope;
9use alloy_genesis::{Genesis, GenesisAccount};
10use alloy_primitives::{Address, B256, Bytes, U256, map::HashMap};
11use alloy_rlp::Decodable;
12use alloy_sol_types::SolValue;
13use foundry_common::fs::{read_json_file, write_json_file};
14use foundry_evm_core::{
15 ContextExt,
16 backend::{DatabaseExt, RevertStateSnapshotAction},
17 constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
18 utils::get_blob_base_fee_update_fraction_by_spec_id,
19};
20use foundry_evm_traces::StackSnapshotType;
21use itertools::Itertools;
22use rand::Rng;
23use revm::{
24 bytecode::Bytecode,
25 context::{Block, JournalTr},
26 primitives::{KECCAK_EMPTY, hardfork::SpecId},
27 state::Account,
28};
29use std::{
30 collections::{BTreeMap, btree_map::Entry},
31 fmt::Display,
32 path::Path,
33};
34
35mod record_debug_step;
36use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace};
37use serde::Serialize;
38
39mod fork;
40pub(crate) mod mapping;
41pub(crate) mod mock;
42pub(crate) mod prank;
43
44#[derive(Clone, Debug, Default)]
46pub struct RecordAccess {
47 pub reads: HashMap<Address, Vec<U256>>,
49 pub writes: HashMap<Address, Vec<U256>>,
51}
52
53impl RecordAccess {
54 pub fn record_read(&mut self, target: Address, slot: U256) {
56 self.reads.entry(target).or_default().push(slot);
57 }
58
59 pub fn record_write(&mut self, target: Address, slot: U256) {
63 self.record_read(target, slot);
64 self.writes.entry(target).or_default().push(slot);
65 }
66
67 pub fn clear(&mut self) {
69 *self = Default::default();
71 }
72}
73
74#[derive(Clone, Debug)]
76pub struct GasRecord {
77 pub group: String,
79 pub name: String,
81 pub gas_used: u64,
83 pub depth: usize,
85}
86
87#[derive(Clone, Debug)]
89pub struct DealRecord {
90 pub address: Address,
92 pub old_balance: U256,
94 pub new_balance: U256,
96}
97
98#[derive(Serialize, Default)]
100#[serde(rename_all = "camelCase")]
101struct SlotStateDiff {
102 previous_value: B256,
104 new_value: B256,
106}
107
108#[derive(Serialize, Default)]
110#[serde(rename_all = "camelCase")]
111struct BalanceDiff {
112 previous_value: U256,
114 new_value: U256,
116}
117
118#[derive(Serialize, Default)]
120#[serde(rename_all = "camelCase")]
121struct AccountStateDiffs {
122 label: Option<String>,
124 balance_diff: Option<BalanceDiff>,
126 state_diff: BTreeMap<B256, SlotStateDiff>,
128}
129
130impl Display for AccountStateDiffs {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
132 if let Some(label) = &self.label {
134 writeln!(f, "label: {label}")?;
135 }
136 if let Some(balance_diff) = &self.balance_diff
138 && balance_diff.previous_value != balance_diff.new_value
139 {
140 writeln!(
141 f,
142 "- balance diff: {} → {}",
143 balance_diff.previous_value, balance_diff.new_value
144 )?;
145 }
146 if !&self.state_diff.is_empty() {
148 writeln!(f, "- state diff:")?;
149 for (slot, slot_changes) in &self.state_diff {
150 writeln!(
151 f,
152 "@ {slot}: {} → {}",
153 slot_changes.previous_value, slot_changes.new_value
154 )?;
155 }
156 }
157
158 Ok(())
159 }
160}
161
162impl Cheatcode for addrCall {
163 fn apply(&self, _state: &mut Cheatcodes) -> Result {
164 let Self { privateKey } = self;
165 let wallet = super::crypto::parse_wallet(privateKey)?;
166 Ok(wallet.address().abi_encode())
167 }
168}
169
170impl Cheatcode for getNonce_0Call {
171 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
172 let Self { account } = self;
173 get_nonce(ccx, account)
174 }
175}
176
177impl Cheatcode for getNonce_1Call {
178 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
179 let Self { wallet } = self;
180 get_nonce(ccx, &wallet.addr)
181 }
182}
183
184impl Cheatcode for loadCall {
185 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
186 let Self { target, slot } = *self;
187 ensure_not_precompile!(&target, ccx);
188 ccx.ecx.journaled_state.load_account(target)?;
189 let mut val = ccx.ecx.journaled_state.sload(target, slot.into())?;
190
191 if val.is_cold && val.data.is_zero() {
192 if ccx.state.has_arbitrary_storage(&target) {
193 let rand_value = ccx.state.rng().random();
196 ccx.state.arbitrary_storage.as_mut().unwrap().save(
197 ccx.ecx,
198 target,
199 slot.into(),
200 rand_value,
201 );
202 val.data = rand_value;
203 } else if ccx.state.is_arbitrary_storage_copy(&target) {
204 let rand_value = ccx.state.rng().random();
208 val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
209 ccx.ecx,
210 target,
211 slot.into(),
212 rand_value,
213 );
214 }
215 }
216
217 Ok(val.abi_encode())
218 }
219}
220
221impl Cheatcode for loadAllocsCall {
222 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
223 let Self { pathToAllocsJson } = self;
224
225 let path = Path::new(pathToAllocsJson);
226 ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
227
228 let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
230 Ok(allocs) => allocs,
231 Err(_) => {
232 let genesis = read_json_file::<Genesis>(path)?;
234 genesis.alloc
235 }
236 };
237
238 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
240 db.load_allocs(&allocs, journal)
241 .map(|()| Vec::default())
242 .map_err(|e| fmt_err!("failed to load allocs: {e}"))
243 }
244}
245
246impl Cheatcode for cloneAccountCall {
247 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
248 let Self { source, target } = self;
249
250 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
251 let account = journal.load_account(db, *source)?;
252 let genesis = &genesis_account(account.data);
253 db.clone_account(genesis, target, journal)?;
254 ccx.ecx.journaled_state.database.add_persistent_account(*target);
256 Ok(Default::default())
257 }
258}
259
260impl Cheatcode for dumpStateCall {
261 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
262 let Self { pathToStateJson } = self;
263 let path = Path::new(pathToStateJson);
264
265 let skip = |key: &Address, val: &Account| {
267 key == &CHEATCODE_ADDRESS
268 || key == &CALLER
269 || key == &HARDHAT_CONSOLE_ADDRESS
270 || key == &TEST_CONTRACT_ADDRESS
271 || key == &ccx.caller
272 || key == &ccx.state.config.evm_opts.sender
273 || val.is_empty()
274 };
275
276 let alloc = ccx
277 .ecx
278 .journaled_state
279 .state()
280 .iter_mut()
281 .filter(|(key, val)| !skip(key, val))
282 .map(|(key, val)| (key, genesis_account(val)))
283 .collect::<BTreeMap<_, _>>();
284
285 write_json_file(path, &alloc)?;
286 Ok(Default::default())
287 }
288}
289
290impl Cheatcode for recordCall {
291 fn apply(&self, state: &mut Cheatcodes) -> Result {
292 let Self {} = self;
293 state.recording_accesses = true;
294 state.accesses.clear();
295 Ok(Default::default())
296 }
297}
298
299impl Cheatcode for stopRecordCall {
300 fn apply(&self, state: &mut Cheatcodes) -> Result {
301 state.recording_accesses = false;
302 Ok(Default::default())
303 }
304}
305
306impl Cheatcode for accessesCall {
307 fn apply(&self, state: &mut Cheatcodes) -> Result {
308 let Self { target } = *self;
309 let result = (
310 state.accesses.reads.entry(target).or_default().as_slice(),
311 state.accesses.writes.entry(target).or_default().as_slice(),
312 );
313 Ok(result.abi_encode_params())
314 }
315}
316
317impl Cheatcode for recordLogsCall {
318 fn apply(&self, state: &mut Cheatcodes) -> Result {
319 let Self {} = self;
320 state.recorded_logs = Some(Default::default());
321 Ok(Default::default())
322 }
323}
324
325impl Cheatcode for getRecordedLogsCall {
326 fn apply(&self, state: &mut Cheatcodes) -> Result {
327 let Self {} = self;
328 Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
329 }
330}
331
332impl Cheatcode for pauseGasMeteringCall {
333 fn apply(&self, state: &mut Cheatcodes) -> Result {
334 let Self {} = self;
335 state.gas_metering.paused = true;
336 Ok(Default::default())
337 }
338}
339
340impl Cheatcode for resumeGasMeteringCall {
341 fn apply(&self, state: &mut Cheatcodes) -> Result {
342 let Self {} = self;
343 state.gas_metering.resume();
344 Ok(Default::default())
345 }
346}
347
348impl Cheatcode for resetGasMeteringCall {
349 fn apply(&self, state: &mut Cheatcodes) -> Result {
350 let Self {} = self;
351 state.gas_metering.reset();
352 Ok(Default::default())
353 }
354}
355
356impl Cheatcode for lastCallGasCall {
357 fn apply(&self, state: &mut Cheatcodes) -> Result {
358 let Self {} = self;
359 let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
360 bail!("no external call was made yet");
361 };
362 Ok(last_call_gas.abi_encode())
363 }
364}
365
366impl Cheatcode for chainIdCall {
367 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
368 let Self { newChainId } = self;
369 ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1");
370 ccx.ecx.cfg.chain_id = newChainId.to();
371 Ok(Default::default())
372 }
373}
374
375impl Cheatcode for coinbaseCall {
376 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
377 let Self { newCoinbase } = self;
378 ccx.ecx.block.beneficiary = *newCoinbase;
379 Ok(Default::default())
380 }
381}
382
383impl Cheatcode for difficultyCall {
384 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
385 let Self { newDifficulty } = self;
386 ensure!(
387 ccx.ecx.cfg.spec < SpecId::MERGE,
388 "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
389 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
390 );
391 ccx.ecx.block.difficulty = *newDifficulty;
392 Ok(Default::default())
393 }
394}
395
396impl Cheatcode for feeCall {
397 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
398 let Self { newBasefee } = self;
399 ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64 - 1");
400 ccx.ecx.block.basefee = newBasefee.saturating_to();
401 Ok(Default::default())
402 }
403}
404
405impl Cheatcode for prevrandao_0Call {
406 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
407 let Self { newPrevrandao } = self;
408 ensure!(
409 ccx.ecx.cfg.spec >= SpecId::MERGE,
410 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
411 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
412 );
413 ccx.ecx.block.prevrandao = Some(*newPrevrandao);
414 Ok(Default::default())
415 }
416}
417
418impl Cheatcode for prevrandao_1Call {
419 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
420 let Self { newPrevrandao } = self;
421 ensure!(
422 ccx.ecx.cfg.spec >= SpecId::MERGE,
423 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
424 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
425 );
426 ccx.ecx.block.prevrandao = Some((*newPrevrandao).into());
427 Ok(Default::default())
428 }
429}
430
431impl Cheatcode for blobhashesCall {
432 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
433 let Self { hashes } = self;
434 ensure!(
435 ccx.ecx.cfg.spec >= SpecId::CANCUN,
436 "`blobhashes` is not supported before the Cancun hard fork; \
437 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
438 );
439 ccx.ecx.tx.blob_hashes.clone_from(hashes);
440 Ok(Default::default())
441 }
442}
443
444impl Cheatcode for getBlobhashesCall {
445 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
446 let Self {} = self;
447 ensure!(
448 ccx.ecx.cfg.spec >= SpecId::CANCUN,
449 "`getBlobhashes` is not supported before the Cancun hard fork; \
450 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
451 );
452 Ok(ccx.ecx.tx.blob_hashes.clone().abi_encode())
453 }
454}
455
456impl Cheatcode for rollCall {
457 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
458 let Self { newHeight } = self;
459 ccx.ecx.block.number = *newHeight;
460 Ok(Default::default())
461 }
462}
463
464impl Cheatcode for getBlockNumberCall {
465 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
466 let Self {} = self;
467 Ok(ccx.ecx.block.number.abi_encode())
468 }
469}
470
471impl Cheatcode for txGasPriceCall {
472 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
473 let Self { newGasPrice } = self;
474 ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64 - 1");
475 ccx.ecx.tx.gas_price = newGasPrice.saturating_to();
476 Ok(Default::default())
477 }
478}
479
480impl Cheatcode for warpCall {
481 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
482 let Self { newTimestamp } = self;
483 ccx.ecx.block.timestamp = *newTimestamp;
484 Ok(Default::default())
485 }
486}
487
488impl Cheatcode for getBlockTimestampCall {
489 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
490 let Self {} = self;
491 Ok(ccx.ecx.block.timestamp.abi_encode())
492 }
493}
494
495impl Cheatcode for blobBaseFeeCall {
496 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
497 let Self { newBlobBaseFee } = self;
498 ensure!(
499 ccx.ecx.cfg.spec >= SpecId::CANCUN,
500 "`blobBaseFee` is not supported before the Cancun hard fork; \
501 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
502 );
503
504 ccx.ecx.block.set_blob_excess_gas_and_price(
505 (*newBlobBaseFee).to(),
506 get_blob_base_fee_update_fraction_by_spec_id(ccx.ecx.cfg.spec),
507 );
508 Ok(Default::default())
509 }
510}
511
512impl Cheatcode for getBlobBaseFeeCall {
513 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
514 let Self {} = self;
515 Ok(ccx.ecx.block.blob_excess_gas().unwrap_or(0).abi_encode())
516 }
517}
518
519impl Cheatcode for dealCall {
520 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
521 let Self { account: address, newBalance: new_balance } = *self;
522 let account = journaled_account(ccx.ecx, address)?;
523 let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
524 let record = DealRecord { address, old_balance, new_balance };
525 ccx.state.eth_deals.push(record);
526 Ok(Default::default())
527 }
528}
529
530impl Cheatcode for etchCall {
531 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
532 let Self { target, newRuntimeBytecode } = self;
533 ensure_not_precompile!(target, ccx);
534 ccx.ecx.journaled_state.load_account(*target)?;
535 let bytecode = Bytecode::new_raw_checked(Bytes::copy_from_slice(newRuntimeBytecode))
536 .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
537 ccx.ecx.journaled_state.set_code(*target, bytecode);
538 Ok(Default::default())
539 }
540}
541
542impl Cheatcode for resetNonceCall {
543 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
544 let Self { account } = self;
545 let account = journaled_account(ccx.ecx, *account)?;
546 let empty = account.info.code_hash == KECCAK_EMPTY;
550 let nonce = if empty { 0 } else { 1 };
551 account.info.nonce = nonce;
552 debug!(target: "cheatcodes", nonce, "reset");
553 Ok(Default::default())
554 }
555}
556
557impl Cheatcode for setNonceCall {
558 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
559 let Self { account, newNonce } = *self;
560 let account = journaled_account(ccx.ecx, account)?;
561 let current = account.info.nonce;
563 ensure!(
564 newNonce >= current,
565 "new nonce ({newNonce}) must be strictly equal to or higher than the \
566 account's current nonce ({current})"
567 );
568 account.info.nonce = newNonce;
569 Ok(Default::default())
570 }
571}
572
573impl Cheatcode for setNonceUnsafeCall {
574 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
575 let Self { account, newNonce } = *self;
576 let account = journaled_account(ccx.ecx, account)?;
577 account.info.nonce = newNonce;
578 Ok(Default::default())
579 }
580}
581
582impl Cheatcode for storeCall {
583 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
584 let Self { target, slot, value } = *self;
585 ensure_not_precompile!(&target, ccx);
586 let _ = journaled_account(ccx.ecx, target)?;
588 ccx.ecx.journaled_state.sstore(target, slot.into(), value.into())?;
589 Ok(Default::default())
590 }
591}
592
593impl Cheatcode for coolCall {
594 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
595 let Self { target } = self;
596 if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) {
597 account.unmark_touch();
598 account.storage.values_mut().for_each(|slot| slot.mark_cold());
599 }
600 Ok(Default::default())
601 }
602}
603
604impl Cheatcode for accessListCall {
605 fn apply(&self, state: &mut Cheatcodes) -> Result {
606 let Self { access } = self;
607 let access_list = access
608 .iter()
609 .map(|item| {
610 let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
611 alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
612 })
613 .collect_vec();
614 state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
615 Ok(Default::default())
616 }
617}
618
619impl Cheatcode for noAccessListCall {
620 fn apply(&self, state: &mut Cheatcodes) -> Result {
621 let Self {} = self;
622 if state.access_list.is_some() {
624 state.access_list = Some(alloy_rpc_types::AccessList::default());
625 }
626 Ok(Default::default())
627 }
628}
629
630impl Cheatcode for warmSlotCall {
631 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
632 let Self { target, slot } = *self;
633 set_cold_slot(ccx, target, slot.into(), false);
634 Ok(Default::default())
635 }
636}
637
638impl Cheatcode for coolSlotCall {
639 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
640 let Self { target, slot } = *self;
641 set_cold_slot(ccx, target, slot.into(), true);
642 Ok(Default::default())
643 }
644}
645
646impl Cheatcode for readCallersCall {
647 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
648 let Self {} = self;
649 read_callers(ccx.state, &ccx.ecx.tx.caller, ccx.ecx.journaled_state.depth())
650 }
651}
652
653impl Cheatcode for snapshotValue_0Call {
654 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
655 let Self { name, value } = self;
656 inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
657 }
658}
659
660impl Cheatcode for snapshotValue_1Call {
661 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
662 let Self { group, name, value } = self;
663 inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
664 }
665}
666
667impl Cheatcode for snapshotGasLastCall_0Call {
668 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
669 let Self { name } = self;
670 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
671 bail!("no external call was made yet");
672 };
673 inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
674 }
675}
676
677impl Cheatcode for snapshotGasLastCall_1Call {
678 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
679 let Self { name, group } = self;
680 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
681 bail!("no external call was made yet");
682 };
683 inner_last_gas_snapshot(
684 ccx,
685 Some(group.clone()),
686 Some(name.clone()),
687 last_call_gas.gasTotalUsed,
688 )
689 }
690}
691
692impl Cheatcode for startSnapshotGas_0Call {
693 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
694 let Self { name } = self;
695 inner_start_gas_snapshot(ccx, None, Some(name.clone()))
696 }
697}
698
699impl Cheatcode for startSnapshotGas_1Call {
700 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
701 let Self { group, name } = self;
702 inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
703 }
704}
705
706impl Cheatcode for stopSnapshotGas_0Call {
707 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
708 let Self {} = self;
709 inner_stop_gas_snapshot(ccx, None, None)
710 }
711}
712
713impl Cheatcode for stopSnapshotGas_1Call {
714 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
715 let Self { name } = self;
716 inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
717 }
718}
719
720impl Cheatcode for stopSnapshotGas_2Call {
721 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
722 let Self { group, name } = self;
723 inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
724 }
725}
726
727impl Cheatcode for snapshotCall {
729 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
730 let Self {} = self;
731 inner_snapshot_state(ccx)
732 }
733}
734
735impl Cheatcode for snapshotStateCall {
736 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
737 let Self {} = self;
738 inner_snapshot_state(ccx)
739 }
740}
741
742impl Cheatcode for revertToCall {
744 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
745 let Self { snapshotId } = self;
746 inner_revert_to_state(ccx, *snapshotId)
747 }
748}
749
750impl Cheatcode for revertToStateCall {
751 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
752 let Self { snapshotId } = self;
753 inner_revert_to_state(ccx, *snapshotId)
754 }
755}
756
757impl Cheatcode for revertToAndDeleteCall {
759 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
760 let Self { snapshotId } = self;
761 inner_revert_to_state_and_delete(ccx, *snapshotId)
762 }
763}
764
765impl Cheatcode for revertToStateAndDeleteCall {
766 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
767 let Self { snapshotId } = self;
768 inner_revert_to_state_and_delete(ccx, *snapshotId)
769 }
770}
771
772impl Cheatcode for deleteSnapshotCall {
774 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
775 let Self { snapshotId } = self;
776 inner_delete_state_snapshot(ccx, *snapshotId)
777 }
778}
779
780impl Cheatcode for deleteStateSnapshotCall {
781 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
782 let Self { snapshotId } = self;
783 inner_delete_state_snapshot(ccx, *snapshotId)
784 }
785}
786
787impl Cheatcode for deleteSnapshotsCall {
789 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
790 let Self {} = self;
791 inner_delete_state_snapshots(ccx)
792 }
793}
794
795impl Cheatcode for deleteStateSnapshotsCall {
796 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
797 let Self {} = self;
798 inner_delete_state_snapshots(ccx)
799 }
800}
801
802impl Cheatcode for startStateDiffRecordingCall {
803 fn apply(&self, state: &mut Cheatcodes) -> Result {
804 let Self {} = self;
805 state.recorded_account_diffs_stack = Some(Default::default());
806 Ok(Default::default())
807 }
808}
809
810impl Cheatcode for stopAndReturnStateDiffCall {
811 fn apply(&self, state: &mut Cheatcodes) -> Result {
812 let Self {} = self;
813 get_state_diff(state)
814 }
815}
816
817impl Cheatcode for getStateDiffCall {
818 fn apply(&self, state: &mut Cheatcodes) -> Result {
819 let mut diffs = String::new();
820 let state_diffs = get_recorded_state_diffs(state);
821 for (address, state_diffs) in state_diffs {
822 diffs.push_str(&format!("{address}\n"));
823 diffs.push_str(&format!("{state_diffs}\n"));
824 }
825 Ok(diffs.abi_encode())
826 }
827}
828
829impl Cheatcode for getStateDiffJsonCall {
830 fn apply(&self, state: &mut Cheatcodes) -> Result {
831 let state_diffs = get_recorded_state_diffs(state);
832 Ok(serde_json::to_string(&state_diffs)?.abi_encode())
833 }
834}
835
836impl Cheatcode for broadcastRawTransactionCall {
837 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
838 let tx = TxEnvelope::decode(&mut self.data.as_ref())
839 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
840
841 let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
842 db.transact_from_tx(
843 &tx.clone().into(),
844 env.to_owned(),
845 journal,
846 &mut *executor.get_inspector(ccx.state),
847 )?;
848
849 if ccx.state.broadcast.is_some() {
850 ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
851 rpc: ccx.ecx.journaled_state.database.active_fork_url(),
852 transaction: tx.try_into()?,
853 });
854 }
855
856 Ok(Default::default())
857 }
858}
859
860impl Cheatcode for setBlockhashCall {
861 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
862 let Self { blockNumber, blockHash } = *self;
863 ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64 - 1");
864 ensure!(
865 blockNumber <= U256::from(ccx.ecx.block.number),
866 "block number must be less than or equal to the current block number"
867 );
868
869 ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash);
870
871 Ok(Default::default())
872 }
873}
874
875impl Cheatcode for startDebugTraceRecordingCall {
876 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
877 let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else {
878 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
879 };
880
881 let mut info = RecordDebugStepInfo {
882 start_node_idx: 0,
884 original_tracer_config: *tracer.config(),
886 };
887
888 tracer.update_config(|config| {
890 config
891 .set_steps(true)
892 .set_memory_snapshots(true)
893 .set_stack_snapshots(StackSnapshotType::Full)
894 });
895
896 if let Some(last_node) = tracer.traces().nodes().last() {
898 info.start_node_idx = last_node.idx;
899 }
900
901 ccx.state.record_debug_steps_info = Some(info);
902 Ok(Default::default())
903 }
904}
905
906impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
907 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
908 let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else {
909 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
910 };
911
912 let Some(record_info) = ccx.state.record_debug_steps_info else {
913 return Err(Error::from("nothing recorded"));
914 };
915
916 let root = tracer.traces();
918 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
919
920 let debug_steps: Vec<DebugStep> =
921 steps.iter().map(|&step| convert_call_trace_to_debug_step(step)).collect();
922 if !record_info.original_tracer_config.record_steps {
924 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
925 node.trace.steps = Vec::new();
926 node.logs = Vec::new();
927 node.ordering = Vec::new();
928 });
929 }
930
931 tracer.update_config(|_config| record_info.original_tracer_config);
933
934 ccx.state.record_debug_steps_info = None;
936
937 Ok(debug_steps.abi_encode())
938 }
939}
940
941pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result {
942 let account = ccx.ecx.journaled_state.load_account(*address)?;
943 Ok(account.info.nonce.abi_encode())
944}
945
946fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result {
947 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
948 Ok(db.snapshot_state(journal, &mut env).abi_encode())
949}
950
951fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
952 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
953 let result = if let Some(journaled_state) =
954 db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep)
955 {
956 ccx.ecx.journaled_state.inner = journaled_state;
958 true
959 } else {
960 false
961 };
962 Ok(result.abi_encode())
963}
964
965fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
966 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
967
968 let result = if let Some(journaled_state) =
969 db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove)
970 {
971 ccx.ecx.journaled_state.inner = journaled_state;
973 true
974 } else {
975 false
976 };
977 Ok(result.abi_encode())
978}
979
980fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
981 let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id);
982 Ok(result.abi_encode())
983}
984
985fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result {
986 ccx.ecx.journaled_state.database.delete_state_snapshots();
987 Ok(Default::default())
988}
989
990fn inner_value_snapshot(
991 ccx: &mut CheatsCtxt,
992 group: Option<String>,
993 name: Option<String>,
994 value: String,
995) -> Result {
996 let (group, name) = derive_snapshot_name(ccx, group, name);
997
998 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
999
1000 Ok(Default::default())
1001}
1002
1003fn inner_last_gas_snapshot(
1004 ccx: &mut CheatsCtxt,
1005 group: Option<String>,
1006 name: Option<String>,
1007 value: u64,
1008) -> Result {
1009 let (group, name) = derive_snapshot_name(ccx, group, name);
1010
1011 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1012
1013 Ok(value.abi_encode())
1014}
1015
1016fn inner_start_gas_snapshot(
1017 ccx: &mut CheatsCtxt,
1018 group: Option<String>,
1019 name: Option<String>,
1020) -> Result {
1021 if ccx.state.gas_metering.active_gas_snapshot.is_some() {
1023 let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone();
1024 bail!("gas snapshot was already started with group: {group} and name: {name}");
1025 }
1026
1027 let (group, name) = derive_snapshot_name(ccx, group, name);
1028
1029 ccx.state.gas_metering.gas_records.push(GasRecord {
1030 group: group.clone(),
1031 name: name.clone(),
1032 gas_used: 0,
1033 depth: ccx.ecx.journaled_state.depth(),
1034 });
1035
1036 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1037
1038 ccx.state.gas_metering.start();
1039
1040 Ok(Default::default())
1041}
1042
1043fn inner_stop_gas_snapshot(
1044 ccx: &mut CheatsCtxt,
1045 group: Option<String>,
1046 name: Option<String>,
1047) -> Result {
1048 let (group, name) = group.zip(name).unwrap_or_else(|| {
1050 let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone();
1051 (group, name)
1052 });
1053
1054 if let Some(record) = ccx
1055 .state
1056 .gas_metering
1057 .gas_records
1058 .iter_mut()
1059 .find(|record| record.group == group && record.name == name)
1060 {
1061 let value = record.gas_used.saturating_sub(171);
1064
1065 ccx.state
1066 .gas_snapshots
1067 .entry(group.clone())
1068 .or_default()
1069 .insert(name.clone(), value.to_string());
1070
1071 ccx.state.gas_metering.stop();
1073
1074 ccx.state
1076 .gas_metering
1077 .gas_records
1078 .retain(|record| record.group != group && record.name != name);
1079
1080 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1082 && snapshot_group == &group
1083 && snapshot_name == &name
1084 {
1085 ccx.state.gas_metering.active_gas_snapshot = None;
1086 }
1087
1088 Ok(value.abi_encode())
1089 } else {
1090 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1091 }
1092}
1093
1094fn derive_snapshot_name(
1096 ccx: &CheatsCtxt,
1097 group: Option<String>,
1098 name: Option<String>,
1099) -> (String, String) {
1100 let group = group.unwrap_or_else(|| {
1101 ccx.state.config.running_artifact.clone().expect("expected running contract").name
1102 });
1103 let name = name.unwrap_or_else(|| "default".to_string());
1104 (group, name)
1105}
1106
1107fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result {
1131 let mut mode = CallerMode::None;
1132 let mut new_caller = default_sender;
1133 let mut new_origin = default_sender;
1134 if let Some(prank) = state.get_prank(call_depth) {
1135 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1136 new_caller = &prank.new_caller;
1137 if let Some(new) = &prank.new_origin {
1138 new_origin = new;
1139 }
1140 } else if let Some(broadcast) = &state.broadcast {
1141 mode = if broadcast.single_call {
1142 CallerMode::Broadcast
1143 } else {
1144 CallerMode::RecurrentBroadcast
1145 };
1146 new_caller = &broadcast.new_origin;
1147 new_origin = &broadcast.new_origin;
1148 }
1149
1150 Ok((mode, new_caller, new_origin).abi_encode_params())
1151}
1152
1153pub(super) fn journaled_account<'a>(
1155 ecx: Ecx<'a, '_, '_>,
1156 addr: Address,
1157) -> Result<&'a mut Account> {
1158 ecx.journaled_state.load_account(addr)?;
1159 ecx.journaled_state.touch(addr);
1160 Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded"))
1161}
1162
1163fn get_state_diff(state: &mut Cheatcodes) -> Result {
1171 let res = state
1172 .recorded_account_diffs_stack
1173 .replace(Default::default())
1174 .unwrap_or_default()
1175 .into_iter()
1176 .flatten()
1177 .collect::<Vec<_>>();
1178 Ok(res.abi_encode())
1179}
1180
1181fn genesis_account(account: &Account) -> GenesisAccount {
1183 GenesisAccount {
1184 nonce: Some(account.info.nonce),
1185 balance: account.info.balance,
1186 code: account.info.code.as_ref().map(|o| o.original_bytes()),
1187 storage: Some(
1188 account
1189 .storage
1190 .iter()
1191 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1192 .collect(),
1193 ),
1194 private_key: None,
1195 }
1196}
1197
1198fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, AccountStateDiffs> {
1200 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1201 if let Some(records) = &state.recorded_account_diffs_stack {
1202 records
1203 .iter()
1204 .flatten()
1205 .filter(|account_access| {
1206 !account_access.storageAccesses.is_empty()
1207 || account_access.oldBalance != account_access.newBalance
1208 })
1209 .for_each(|account_access| {
1210 if account_access.oldBalance != account_access.newBalance {
1212 let account_diff =
1213 state_diffs.entry(account_access.account).or_insert_with(|| {
1214 AccountStateDiffs {
1215 label: state.labels.get(&account_access.account).cloned(),
1216 ..Default::default()
1217 }
1218 });
1219 if let Some(diff) = &mut account_diff.balance_diff {
1221 diff.new_value = account_access.newBalance;
1222 } else {
1223 account_diff.balance_diff = Some(BalanceDiff {
1224 previous_value: account_access.oldBalance,
1225 new_value: account_access.newBalance,
1226 });
1227 }
1228 }
1229
1230 for storage_access in &account_access.storageAccesses {
1232 if storage_access.isWrite && !storage_access.reverted {
1233 let account_diff = state_diffs
1234 .entry(storage_access.account)
1235 .or_insert_with(|| AccountStateDiffs {
1236 label: state.labels.get(&storage_access.account).cloned(),
1237 ..Default::default()
1238 });
1239 match account_diff.state_diff.entry(storage_access.slot) {
1241 Entry::Vacant(slot_state_diff) => {
1242 slot_state_diff.insert(SlotStateDiff {
1243 previous_value: storage_access.previousValue,
1244 new_value: storage_access.newValue,
1245 });
1246 }
1247 Entry::Occupied(mut slot_state_diff) => {
1248 slot_state_diff.get_mut().new_value = storage_access.newValue;
1249 }
1250 }
1251 }
1252 }
1253 });
1254 }
1255 state_diffs
1256}
1257
1258fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) {
1260 if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target)
1261 && let Some(storage_slot) = account.storage.get_mut(&slot)
1262 {
1263 storage_slot.is_cold = cold;
1264 }
1265}