foundry_cheatcodes/
evm.rs

1//! Implementations of [`Evm`](spec::Group::Evm) cheatcodes.
2
3use 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/// Records storage slots reads and writes.
38#[derive(Clone, Debug, Default)]
39pub struct RecordAccess {
40    /// Storage slots reads.
41    pub reads: HashMap<Address, Vec<U256>>,
42    /// Storage slots writes.
43    pub writes: HashMap<Address, Vec<U256>>,
44}
45
46impl RecordAccess {
47    /// Records a read access to a storage slot.
48    pub fn record_read(&mut self, target: Address, slot: U256) {
49        self.reads.entry(target).or_default().push(slot);
50    }
51
52    /// Records a write access to a storage slot.
53    ///
54    /// This also records a read internally as `SSTORE` does an implicit `SLOAD`.
55    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/// Records the `snapshotGas*` cheatcodes.
62#[derive(Clone, Debug)]
63pub struct GasRecord {
64    /// The group name of the gas snapshot.
65    pub group: String,
66    /// The name of the gas snapshot.
67    pub name: String,
68    /// The total gas used in the gas snapshot.
69    pub gas_used: u64,
70    /// Depth at which the gas snapshot was taken.
71    pub depth: u64,
72}
73
74/// Records `deal` cheatcodes
75#[derive(Clone, Debug)]
76pub struct DealRecord {
77    /// Target of the deal.
78    pub address: Address,
79    /// The balance of the address before deal was applied
80    pub old_balance: U256,
81    /// Balance after deal was applied
82    pub new_balance: U256,
83}
84
85/// Storage slot diff info.
86#[derive(Serialize, Default)]
87#[serde(rename_all = "camelCase")]
88struct SlotStateDiff {
89    /// Initial storage value.
90    previous_value: B256,
91    /// Current storage value.
92    new_value: B256,
93}
94
95/// Balance diff info.
96#[derive(Serialize, Default)]
97#[serde(rename_all = "camelCase")]
98struct BalanceDiff {
99    /// Initial storage value.
100    previous_value: U256,
101    /// Current storage value.
102    new_value: U256,
103}
104
105/// Account state diff info.
106#[derive(Serialize, Default)]
107#[serde(rename_all = "camelCase")]
108struct AccountStateDiffs {
109    /// Address label, if any set.
110    label: Option<String>,
111    /// Account balance changes.
112    balance_diff: Option<BalanceDiff>,
113    /// State changes, per slot.
114    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        // Print changed account.
120        if let Some(label) = &self.label {
121            writeln!(f, "label: {label}")?;
122        }
123        // Print balance diff if changed.
124        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        // Print state diff if any.
134        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                // If storage slot is untouched and load from a target with arbitrary storage,
181                // then set random value for current slot.
182                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                // If storage slot is untouched and load from a target that copies storage from
192                // a source address with arbitrary storage, then copy existing arbitrary value.
193                // If no arbitrary value generated yet, then the random one is saved and set.
194                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's first assume we're reading a file with only the allocs.
216        let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
217            Ok(allocs) => allocs,
218            Err(_) => {
219                // Let's try and read from a genesis file, and extract allocs.
220                let genesis = read_json_file::<Genesis>(path)?;
221                genesis.alloc
222            }
223        };
224
225        // Then, load the allocs into the database.
226        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        // Cloned account should persist in forked envs.
245        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        // Do not include system account or empty accounts in the dump.
256        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        // Per EIP-161, EOA nonces start at 0, but contract nonces
532        // start at 1. Comparing by code_hash instead of code
533        // to avoid hitting the case where account's code is None.
534        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        // nonce must increment only
547        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        // ensure the account is touched
572        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        // Set to empty option in order to override previous applied access list.
608        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
712// Deprecated in favor of `snapshotStateCall`
713impl 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
727// Deprecated in favor of `revertToStateCall`
728impl 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
742// Deprecated in favor of `revertToStateAndDeleteCall`
743impl 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
757// Deprecated in favor of `deleteStateSnapshotCall`
758impl 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
772// Deprecated in favor of `deleteStateSnapshotsCall`
773impl 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            // will be updated later
866            start_node_idx: 0,
867            // keep the original config to revert back later
868            original_tracer_config: *tracer.config(),
869        };
870
871        // turn on tracer configuration for recording
872        tracer.update_config(|config| {
873            config
874                .set_steps(true)
875                .set_memory_snapshots(true)
876                .set_stack_snapshots(StackSnapshotType::Full)
877        });
878
879        // track where the recording starts
880        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        // Use the trace nodes to flatten the call trace
900        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        // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage.
906        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        // Revert the tracer config to the one before recording
915        tracer.update_config(|_config| record_info.original_tracer_config);
916
917        // Clean up the recording info
918        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        // we reset the evm's journaled_state to the state of the snapshot previous state
941        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        // we reset the evm's journaled_state to the state of the snapshot previous state
957        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    // Revert if there is an active gas snapshot as we can only have one active snapshot at a time.
1007    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    // If group and name are not provided, use the last snapshot group and name.
1034    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        // Calculate the gas used since the snapshot was started.
1047        // We subtract 171 from the gas used to account for gas used by the snapshot itself.
1048        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        // Stop the gas metering.
1057        ccx.state.gas_metering.stop();
1058
1059        // Remove the gas record.
1060        ccx.state
1061            .gas_metering
1062            .gas_records
1063            .retain(|record| record.group != group && record.name != name);
1064
1065        // Clear last snapshot cache if we have an exact match.
1066        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
1078// Derives the snapshot group and name from the provided group and name or the running contract.
1079fn 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
1091/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and
1092/// `tx.origin`.
1093///
1094/// Depending on the current caller mode, one of the following results will be returned:
1095/// - If there is an active prank:
1096///     - caller_mode will be equal to:
1097///         - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`.
1098///         - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`.
1099///     - `msg.sender` will be equal to the address set for the prank.
1100///     - `tx.origin` will be equal to the default sender address unless an alternative one has been
1101///       set when configuring the prank.
1102///
1103/// - If there is an active broadcast:
1104///     - caller_mode will be equal to:
1105///         - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`.
1106///         - [CallerMode::RecurrentBroadcast] if the broadcast has been set with
1107///           `vm.startBroadcast(..)`.
1108///     - `msg.sender` and `tx.origin` will be equal to the address provided when setting the
1109///       broadcast.
1110///
1111/// - If no caller modification is active:
1112///     - caller_mode will be equal to [CallerMode::None],
1113///     - `msg.sender` and `tx.origin` will be equal to the default sender address.
1114fn 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
1137/// Ensures the `Account` is loaded and touched.
1138pub(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
1147/// Consumes recorded account accesses and returns them as an abi encoded
1148/// array of [AccountAccess]. If there are no accounts were
1149/// recorded as accessed, an abi encoded empty array is returned.
1150///
1151/// In the case where `stopAndReturnStateDiff` is called at a lower
1152/// depth than `startStateDiffRecording`, multiple `Vec<RecordedAccountAccesses>`
1153/// will be flattened, preserving the order of the accesses.
1154fn 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
1165/// Helper function that creates a `GenesisAccount` from a regular `Account`.
1166fn 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
1182/// Helper function to returns state diffs recorded for each changed account.
1183fn 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                // Record account balance diffs.
1195                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                    // Update balance diff. Do not overwrite the initial balance if already set.
1204                    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                // Record account state diffs.
1215                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                        // Update state diff. Do not overwrite the initial value if already set.
1224                        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
1242/// Helper function to set / unset cold storage slot of the target address.
1243fn 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}