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