foundry_cheatcodes/
evm.rs

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