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