Skip to main content

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_evm::{Evm as _, FromRecoveredTx};
10use alloy_genesis::{Genesis, GenesisAccount};
11use alloy_network::eip2718::EIP4844_TX_TYPE_ID;
12use alloy_primitives::{
13    Address, B256, U256, hex, keccak256,
14    map::{B256Map, HashMap},
15};
16use alloy_rlp::Decodable;
17use alloy_sol_types::SolValue;
18use foundry_common::{
19    fs::{read_json_file, write_json_file},
20    slot_identifier::{
21        ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier,
22        SlotInfo,
23    },
24};
25use foundry_compilers::artifacts::EvmVersion;
26use foundry_evm_core::{
27    ContextExt,
28    backend::{DatabaseExt, RevertStateSnapshotAction},
29    constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
30    evm::new_evm_with_inspector,
31    utils::get_blob_base_fee_update_fraction_by_spec_id,
32};
33use foundry_evm_traces::TraceMode;
34use foundry_primitives::FoundryTxEnvelope;
35use itertools::Itertools;
36use rand::Rng;
37use revm::{
38    bytecode::Bytecode,
39    context::{Block, ContextTr, JournalTr, TxEnv, result::ExecutionResult},
40    primitives::{KECCAK_EMPTY, hardfork::SpecId},
41    state::{Account, AccountStatus},
42};
43use std::{
44    collections::{BTreeMap, HashSet, btree_map::Entry},
45    fmt::Display,
46    path::Path,
47    str::FromStr,
48};
49
50mod record_debug_step;
51use foundry_common::fmt::format_token_raw;
52use foundry_config::evm_spec_id;
53use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
54use serde::Serialize;
55
56mod fork;
57pub(crate) mod mapping;
58pub(crate) mod mock;
59pub(crate) mod prank;
60
61/// JSON-serializable log entry for `getRecordedLogsJson`.
62#[derive(Serialize)]
63#[serde(rename_all = "camelCase")]
64struct LogJson {
65    /// The topics of the log, including the signature, if any.
66    topics: Vec<String>,
67    /// The raw data of the log, hex-encoded with 0x prefix.
68    data: String,
69    /// The address of the log's emitter.
70    emitter: String,
71}
72
73/// Records storage slots reads and writes.
74#[derive(Clone, Debug, Default)]
75pub struct RecordAccess {
76    /// Storage slots reads.
77    pub reads: HashMap<Address, Vec<U256>>,
78    /// Storage slots writes.
79    pub writes: HashMap<Address, Vec<U256>>,
80}
81
82impl RecordAccess {
83    /// Records a read access to a storage slot.
84    pub fn record_read(&mut self, target: Address, slot: U256) {
85        self.reads.entry(target).or_default().push(slot);
86    }
87
88    /// Records a write access to a storage slot.
89    ///
90    /// This also records a read internally as `SSTORE` does an implicit `SLOAD`.
91    pub fn record_write(&mut self, target: Address, slot: U256) {
92        self.record_read(target, slot);
93        self.writes.entry(target).or_default().push(slot);
94    }
95
96    /// Clears the recorded reads and writes.
97    pub fn clear(&mut self) {
98        // Also frees memory.
99        *self = Default::default();
100    }
101}
102
103/// Records the `snapshotGas*` cheatcodes.
104#[derive(Clone, Debug)]
105pub struct GasRecord {
106    /// The group name of the gas snapshot.
107    pub group: String,
108    /// The name of the gas snapshot.
109    pub name: String,
110    /// The total gas used in the gas snapshot.
111    pub gas_used: u64,
112    /// Depth at which the gas snapshot was taken.
113    pub depth: usize,
114}
115
116/// Records `deal` cheatcodes
117#[derive(Clone, Debug)]
118pub struct DealRecord {
119    /// Target of the deal.
120    pub address: Address,
121    /// The balance of the address before deal was applied
122    pub old_balance: U256,
123    /// Balance after deal was applied
124    pub new_balance: U256,
125}
126
127/// Storage slot diff info.
128#[derive(Serialize, Default)]
129#[serde(rename_all = "camelCase")]
130struct SlotStateDiff {
131    /// Initial storage value.
132    previous_value: B256,
133    /// Current storage value.
134    new_value: B256,
135    /// Storage layout metadata (variable name, type, offset).
136    /// Only present when contract has storage layout output.
137    /// This includes decoded values when available.
138    #[serde(skip_serializing_if = "Option::is_none", flatten)]
139    slot_info: Option<SlotInfo>,
140}
141
142/// Balance diff info.
143#[derive(Serialize, Default)]
144#[serde(rename_all = "camelCase")]
145struct BalanceDiff {
146    /// Initial storage value.
147    previous_value: U256,
148    /// Current storage value.
149    new_value: U256,
150}
151
152/// Nonce diff info.
153#[derive(Serialize, Default)]
154#[serde(rename_all = "camelCase")]
155struct NonceDiff {
156    /// Initial nonce value.
157    previous_value: u64,
158    /// Current nonce value.
159    new_value: u64,
160}
161
162/// Account state diff info.
163#[derive(Serialize, Default)]
164#[serde(rename_all = "camelCase")]
165struct AccountStateDiffs {
166    /// Address label, if any set.
167    label: Option<String>,
168    /// Contract identifier from artifact. e.g "src/Counter.sol:Counter"
169    contract: Option<String>,
170    /// Account balance changes.
171    balance_diff: Option<BalanceDiff>,
172    /// Account nonce changes.
173    nonce_diff: Option<NonceDiff>,
174    /// State changes, per slot.
175    state_diff: BTreeMap<B256, SlotStateDiff>,
176}
177
178impl Display for AccountStateDiffs {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
180        // Print changed account.
181        if let Some(label) = &self.label {
182            writeln!(f, "label: {label}")?;
183        }
184        if let Some(contract) = &self.contract {
185            writeln!(f, "contract: {contract}")?;
186        }
187        // Print balance diff if changed.
188        if let Some(balance_diff) = &self.balance_diff
189            && balance_diff.previous_value != balance_diff.new_value
190        {
191            writeln!(
192                f,
193                "- balance diff: {} → {}",
194                balance_diff.previous_value, balance_diff.new_value
195            )?;
196        }
197        // Print nonce diff if changed.
198        if let Some(nonce_diff) = &self.nonce_diff
199            && nonce_diff.previous_value != nonce_diff.new_value
200        {
201            writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
202        }
203        // Print state diff if any.
204        if !&self.state_diff.is_empty() {
205            writeln!(f, "- state diff:")?;
206            for (slot, slot_changes) in &self.state_diff {
207                match &slot_changes.slot_info {
208                    Some(slot_info) => {
209                        if let Some(decoded) = &slot_info.decoded {
210                            // Have slot info with decoded values - show decoded values
211                            writeln!(
212                                f,
213                                "@ {slot} ({}, {}): {} → {}",
214                                slot_info.label,
215                                slot_info.slot_type.dyn_sol_type,
216                                format_token_raw(&decoded.previous_value),
217                                format_token_raw(&decoded.new_value)
218                            )?;
219                        } else {
220                            // Have slot info but no decoded values - show raw hex values
221                            writeln!(
222                                f,
223                                "@ {slot} ({}, {}): {} → {}",
224                                slot_info.label,
225                                slot_info.slot_type.dyn_sol_type,
226                                slot_changes.previous_value,
227                                slot_changes.new_value
228                            )?;
229                        }
230                    }
231                    None => {
232                        // No slot info - show raw hex values
233                        writeln!(
234                            f,
235                            "@ {slot}: {} → {}",
236                            slot_changes.previous_value, slot_changes.new_value
237                        )?;
238                    }
239                }
240            }
241        }
242
243        Ok(())
244    }
245}
246
247impl Cheatcode for addrCall {
248    fn apply(&self, _state: &mut Cheatcodes) -> Result {
249        let Self { privateKey } = self;
250        let wallet = super::crypto::parse_wallet(privateKey)?;
251        Ok(wallet.address().abi_encode())
252    }
253}
254
255impl Cheatcode for getNonce_0Call {
256    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
257        let Self { account } = self;
258        get_nonce(ccx, account)
259    }
260}
261
262impl Cheatcode for getNonce_1Call {
263    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
264        let Self { wallet } = self;
265        get_nonce(ccx, &wallet.addr)
266    }
267}
268
269impl Cheatcode for loadCall {
270    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
271        let Self { target, slot } = *self;
272        ccx.ensure_not_precompile(&target)?;
273
274        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
275        journal.load_account(db, target)?;
276        let mut val = journal
277            .sload(db, target, slot.into(), false)
278            .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
279
280        if val.is_cold && val.data.is_zero() {
281            if ccx.state.has_arbitrary_storage(&target) {
282                // If storage slot is untouched and load from a target with arbitrary storage,
283                // then set random value for current slot.
284                let rand_value = ccx.state.rng().random();
285                ccx.state.arbitrary_storage.as_mut().unwrap().save(
286                    ccx.ecx,
287                    target,
288                    slot.into(),
289                    rand_value,
290                );
291                val.data = rand_value;
292            } else if ccx.state.is_arbitrary_storage_copy(&target) {
293                // If storage slot is untouched and load from a target that copies storage from
294                // a source address with arbitrary storage, then copy existing arbitrary value.
295                // If no arbitrary value generated yet, then the random one is saved and set.
296                let rand_value = ccx.state.rng().random();
297                val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
298                    ccx.ecx,
299                    target,
300                    slot.into(),
301                    rand_value,
302                );
303            }
304        }
305
306        Ok(val.abi_encode())
307    }
308}
309
310impl Cheatcode for loadAllocsCall {
311    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
312        let Self { pathToAllocsJson } = self;
313
314        let path = Path::new(pathToAllocsJson);
315        ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
316
317        // Let's first assume we're reading a file with only the allocs.
318        let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
319            Ok(allocs) => allocs,
320            Err(_) => {
321                // Let's try and read from a genesis file, and extract allocs.
322                let genesis = read_json_file::<Genesis>(path)?;
323                genesis.alloc
324            }
325        };
326
327        // Then, load the allocs into the database.
328        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
329        db.load_allocs(&allocs, journal)
330            .map(|()| Vec::default())
331            .map_err(|e| fmt_err!("failed to load allocs: {e}"))
332    }
333}
334
335impl Cheatcode for cloneAccountCall {
336    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
337        let Self { source, target } = self;
338
339        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
340        let account = journal.load_account(db, *source)?;
341        let genesis = &genesis_account(account.data);
342        db.clone_account(genesis, target, journal)?;
343        // Cloned account should persist in forked envs.
344        ccx.ecx.journaled_state.database.add_persistent_account(*target);
345        Ok(Default::default())
346    }
347}
348
349impl Cheatcode for dumpStateCall {
350    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
351        let Self { pathToStateJson } = self;
352        let path = Path::new(pathToStateJson);
353
354        // Do not include system account or empty accounts in the dump.
355        let skip = |key: &Address, val: &Account| {
356            key == &CHEATCODE_ADDRESS
357                || key == &CALLER
358                || key == &HARDHAT_CONSOLE_ADDRESS
359                || key == &TEST_CONTRACT_ADDRESS
360                || key == &ccx.caller
361                || key == &ccx.state.config.evm_opts.sender
362                || val.is_empty()
363        };
364
365        let alloc = ccx
366            .ecx
367            .journaled_state
368            .state()
369            .iter_mut()
370            .filter(|(key, val)| !skip(key, val))
371            .map(|(key, val)| (key, genesis_account(val)))
372            .collect::<BTreeMap<_, _>>();
373
374        write_json_file(path, &alloc)?;
375        Ok(Default::default())
376    }
377}
378
379impl Cheatcode for recordCall {
380    fn apply(&self, state: &mut Cheatcodes) -> Result {
381        let Self {} = self;
382        state.recording_accesses = true;
383        state.accesses.clear();
384        Ok(Default::default())
385    }
386}
387
388impl Cheatcode for stopRecordCall {
389    fn apply(&self, state: &mut Cheatcodes) -> Result {
390        state.recording_accesses = false;
391        Ok(Default::default())
392    }
393}
394
395impl Cheatcode for accessesCall {
396    fn apply(&self, state: &mut Cheatcodes) -> Result {
397        let Self { target } = *self;
398        let result = (
399            state.accesses.reads.entry(target).or_default().as_slice(),
400            state.accesses.writes.entry(target).or_default().as_slice(),
401        );
402        Ok(result.abi_encode_params())
403    }
404}
405
406impl Cheatcode for recordLogsCall {
407    fn apply(&self, state: &mut Cheatcodes) -> Result {
408        let Self {} = self;
409        state.recorded_logs = Some(Default::default());
410        Ok(Default::default())
411    }
412}
413
414impl Cheatcode for getRecordedLogsCall {
415    fn apply(&self, state: &mut Cheatcodes) -> Result {
416        let Self {} = self;
417        Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
418    }
419}
420
421impl Cheatcode for getRecordedLogsJsonCall {
422    fn apply(&self, state: &mut Cheatcodes) -> Result {
423        let Self {} = self;
424        let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default();
425        let json_logs: Vec<_> = logs
426            .into_iter()
427            .map(|log| LogJson {
428                topics: log.topics.iter().map(|t| format!("{t}")).collect(),
429                data: hex::encode_prefixed(&log.data),
430                emitter: format!("{}", log.emitter),
431            })
432            .collect();
433        Ok(serde_json::to_string(&json_logs)?.abi_encode())
434    }
435}
436
437impl Cheatcode for pauseGasMeteringCall {
438    fn apply(&self, state: &mut Cheatcodes) -> Result {
439        let Self {} = self;
440        state.gas_metering.paused = true;
441        Ok(Default::default())
442    }
443}
444
445impl Cheatcode for resumeGasMeteringCall {
446    fn apply(&self, state: &mut Cheatcodes) -> Result {
447        let Self {} = self;
448        state.gas_metering.resume();
449        Ok(Default::default())
450    }
451}
452
453impl Cheatcode for resetGasMeteringCall {
454    fn apply(&self, state: &mut Cheatcodes) -> Result {
455        let Self {} = self;
456        state.gas_metering.reset();
457        Ok(Default::default())
458    }
459}
460
461impl Cheatcode for lastCallGasCall {
462    fn apply(&self, state: &mut Cheatcodes) -> Result {
463        let Self {} = self;
464        let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
465            bail!("no external call was made yet");
466        };
467        Ok(last_call_gas.abi_encode())
468    }
469}
470
471impl Cheatcode for getChainIdCall {
472    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
473        let Self {} = self;
474        Ok(U256::from(ccx.ecx.cfg().chain_id).abi_encode())
475    }
476}
477
478impl Cheatcode for chainIdCall {
479    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
480        let Self { newChainId } = self;
481        ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
482        ccx.ecx.cfg.chain_id = newChainId.to();
483        Ok(Default::default())
484    }
485}
486
487impl Cheatcode for coinbaseCall {
488    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
489        let Self { newCoinbase } = self;
490        ccx.ecx.block.beneficiary = *newCoinbase;
491        Ok(Default::default())
492    }
493}
494
495impl Cheatcode for difficultyCall {
496    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
497        let Self { newDifficulty } = self;
498        ensure!(
499            ccx.ecx.cfg().spec < SpecId::MERGE,
500            "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
501             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
502        );
503        ccx.ecx.block.difficulty = *newDifficulty;
504        Ok(Default::default())
505    }
506}
507
508impl Cheatcode for feeCall {
509    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
510        let Self { newBasefee } = self;
511        ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
512        ccx.ecx.block.basefee = newBasefee.saturating_to();
513        Ok(Default::default())
514    }
515}
516
517impl Cheatcode for prevrandao_0Call {
518    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
519        let Self { newPrevrandao } = self;
520        ensure!(
521            ccx.ecx.cfg().spec >= SpecId::MERGE,
522            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
523             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
524        );
525        ccx.ecx.block.prevrandao = Some(*newPrevrandao);
526        Ok(Default::default())
527    }
528}
529
530impl Cheatcode for prevrandao_1Call {
531    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
532        let Self { newPrevrandao } = self;
533        ensure!(
534            ccx.ecx.cfg().spec >= SpecId::MERGE,
535            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
536             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
537        );
538        ccx.ecx.block.prevrandao = Some((*newPrevrandao).into());
539        Ok(Default::default())
540    }
541}
542
543impl Cheatcode for blobhashesCall {
544    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
545        let Self { hashes } = self;
546        ensure!(
547            ccx.ecx.cfg().spec >= SpecId::CANCUN,
548            "`blobhashes` is not supported before the Cancun hard fork; \
549             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
550        );
551        ccx.ecx.tx.blob_hashes.clone_from(hashes);
552        // force this as 4844 txtype
553        ccx.ecx.tx.tx_type = EIP4844_TX_TYPE_ID;
554        Ok(Default::default())
555    }
556}
557
558impl Cheatcode for getBlobhashesCall {
559    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
560        let Self {} = self;
561        ensure!(
562            ccx.ecx.cfg().spec >= SpecId::CANCUN,
563            "`getBlobhashes` is not supported before the Cancun hard fork; \
564             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
565        );
566        Ok(ccx.ecx.tx().blob_hashes.clone().abi_encode())
567    }
568}
569
570impl Cheatcode for rollCall {
571    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
572        let Self { newHeight } = self;
573        ccx.ecx.block.number = *newHeight;
574        Ok(Default::default())
575    }
576}
577
578impl Cheatcode for getBlockNumberCall {
579    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
580        let Self {} = self;
581        Ok(ccx.ecx.block().number.abi_encode())
582    }
583}
584
585impl Cheatcode for txGasPriceCall {
586    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
587        let Self { newGasPrice } = self;
588        ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
589        ccx.ecx.tx.gas_price = newGasPrice.saturating_to();
590        Ok(Default::default())
591    }
592}
593
594impl Cheatcode for warpCall {
595    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
596        let Self { newTimestamp } = self;
597        ccx.ecx.block.timestamp = *newTimestamp;
598        Ok(Default::default())
599    }
600}
601
602impl Cheatcode for getBlockTimestampCall {
603    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
604        let Self {} = self;
605        Ok(ccx.ecx.block().timestamp.abi_encode())
606    }
607}
608
609impl Cheatcode for blobBaseFeeCall {
610    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
611        let Self { newBlobBaseFee } = self;
612        ensure!(
613            ccx.ecx.cfg().spec >= SpecId::CANCUN,
614            "`blobBaseFee` is not supported before the Cancun hard fork; \
615             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
616        );
617
618        ccx.ecx.block.set_blob_excess_gas_and_price(
619            (*newBlobBaseFee).to(),
620            get_blob_base_fee_update_fraction_by_spec_id(ccx.ecx.cfg().spec),
621        );
622        Ok(Default::default())
623    }
624}
625
626impl Cheatcode for getBlobBaseFeeCall {
627    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
628        let Self {} = self;
629        Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode())
630    }
631}
632
633impl Cheatcode for dealCall {
634    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
635        let Self { account: address, newBalance: new_balance } = *self;
636        let account = journaled_account(ccx.ecx, address)?;
637        let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
638        let record = DealRecord { address, old_balance, new_balance };
639        ccx.state.eth_deals.push(record);
640        Ok(Default::default())
641    }
642}
643
644impl Cheatcode for etchCall {
645    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
646        let Self { target, newRuntimeBytecode } = self;
647        ccx.ensure_not_precompile(target)?;
648        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
649        journal.load_account(db, *target)?;
650        let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
651            .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
652        journal.set_code(*target, bytecode);
653        Ok(Default::default())
654    }
655}
656
657impl Cheatcode for resetNonceCall {
658    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
659        let Self { account } = self;
660        let account = journaled_account(ccx.ecx, *account)?;
661        // Per EIP-161, EOA nonces start at 0, but contract nonces
662        // start at 1. Comparing by code_hash instead of code
663        // to avoid hitting the case where account's code is None.
664        let empty = account.info.code_hash == KECCAK_EMPTY;
665        let nonce = if empty { 0 } else { 1 };
666        account.info.nonce = nonce;
667        debug!(target: "cheatcodes", nonce, "reset");
668        Ok(Default::default())
669    }
670}
671
672impl Cheatcode for setNonceCall {
673    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
674        let Self { account, newNonce } = *self;
675        let account = journaled_account(ccx.ecx, account)?;
676        // nonce must increment only
677        let current = account.info.nonce;
678        ensure!(
679            newNonce >= current,
680            "new nonce ({newNonce}) must be strictly equal to or higher than the \
681             account's current nonce ({current})"
682        );
683        account.info.nonce = newNonce;
684        Ok(Default::default())
685    }
686}
687
688impl Cheatcode for setNonceUnsafeCall {
689    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
690        let Self { account, newNonce } = *self;
691        let account = journaled_account(ccx.ecx, account)?;
692        account.info.nonce = newNonce;
693        Ok(Default::default())
694    }
695}
696
697impl Cheatcode for storeCall {
698    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
699        let Self { target, slot, value } = *self;
700        ccx.ensure_not_precompile(&target)?;
701        ensure_loaded_account(ccx.ecx, target)?;
702        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
703        journal
704            .sstore(db, target, slot.into(), value.into(), false)
705            .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
706        Ok(Default::default())
707    }
708}
709
710impl Cheatcode for coolCall {
711    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
712        let Self { target } = self;
713        if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) {
714            account.unmark_touch();
715            account.storage.values_mut().for_each(|slot| slot.mark_cold());
716        }
717        Ok(Default::default())
718    }
719}
720
721impl Cheatcode for accessListCall {
722    fn apply(&self, state: &mut Cheatcodes) -> Result {
723        let Self { access } = self;
724        let access_list = access
725            .iter()
726            .map(|item| {
727                let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
728                alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
729            })
730            .collect_vec();
731        state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
732        Ok(Default::default())
733    }
734}
735
736impl Cheatcode for noAccessListCall {
737    fn apply(&self, state: &mut Cheatcodes) -> Result {
738        let Self {} = self;
739        // Set to empty option in order to override previous applied access list.
740        if state.access_list.is_some() {
741            state.access_list = Some(alloy_rpc_types::AccessList::default());
742        }
743        Ok(Default::default())
744    }
745}
746
747impl Cheatcode for warmSlotCall {
748    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
749        let Self { target, slot } = *self;
750        set_cold_slot(ccx, target, slot.into(), false);
751        Ok(Default::default())
752    }
753}
754
755impl Cheatcode for coolSlotCall {
756    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
757        let Self { target, slot } = *self;
758        set_cold_slot(ccx, target, slot.into(), true);
759        Ok(Default::default())
760    }
761}
762
763impl Cheatcode for readCallersCall {
764    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
765        let Self {} = self;
766        read_callers(ccx.state, &ccx.ecx.tx().caller, ccx.ecx.journaled_state.depth())
767    }
768}
769
770impl Cheatcode for snapshotValue_0Call {
771    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
772        let Self { name, value } = self;
773        inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
774    }
775}
776
777impl Cheatcode for snapshotValue_1Call {
778    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
779        let Self { group, name, value } = self;
780        inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
781    }
782}
783
784impl Cheatcode for snapshotGasLastCall_0Call {
785    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
786        let Self { name } = self;
787        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
788            bail!("no external call was made yet");
789        };
790        inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
791    }
792}
793
794impl Cheatcode for snapshotGasLastCall_1Call {
795    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
796        let Self { name, group } = self;
797        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
798            bail!("no external call was made yet");
799        };
800        inner_last_gas_snapshot(
801            ccx,
802            Some(group.clone()),
803            Some(name.clone()),
804            last_call_gas.gasTotalUsed,
805        )
806    }
807}
808
809impl Cheatcode for startSnapshotGas_0Call {
810    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
811        let Self { name } = self;
812        inner_start_gas_snapshot(ccx, None, Some(name.clone()))
813    }
814}
815
816impl Cheatcode for startSnapshotGas_1Call {
817    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
818        let Self { group, name } = self;
819        inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
820    }
821}
822
823impl Cheatcode for stopSnapshotGas_0Call {
824    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
825        let Self {} = self;
826        inner_stop_gas_snapshot(ccx, None, None)
827    }
828}
829
830impl Cheatcode for stopSnapshotGas_1Call {
831    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
832        let Self { name } = self;
833        inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
834    }
835}
836
837impl Cheatcode for stopSnapshotGas_2Call {
838    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
839        let Self { group, name } = self;
840        inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
841    }
842}
843
844// Deprecated in favor of `snapshotStateCall`
845impl Cheatcode for snapshotCall {
846    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
847        let Self {} = self;
848        inner_snapshot_state(ccx)
849    }
850}
851
852impl Cheatcode for snapshotStateCall {
853    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
854        let Self {} = self;
855        inner_snapshot_state(ccx)
856    }
857}
858
859// Deprecated in favor of `revertToStateCall`
860impl Cheatcode for revertToCall {
861    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
862        let Self { snapshotId } = self;
863        inner_revert_to_state(ccx, *snapshotId)
864    }
865}
866
867impl Cheatcode for revertToStateCall {
868    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
869        let Self { snapshotId } = self;
870        inner_revert_to_state(ccx, *snapshotId)
871    }
872}
873
874// Deprecated in favor of `revertToStateAndDeleteCall`
875impl Cheatcode for revertToAndDeleteCall {
876    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
877        let Self { snapshotId } = self;
878        inner_revert_to_state_and_delete(ccx, *snapshotId)
879    }
880}
881
882impl Cheatcode for revertToStateAndDeleteCall {
883    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
884        let Self { snapshotId } = self;
885        inner_revert_to_state_and_delete(ccx, *snapshotId)
886    }
887}
888
889// Deprecated in favor of `deleteStateSnapshotCall`
890impl Cheatcode for deleteSnapshotCall {
891    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
892        let Self { snapshotId } = self;
893        inner_delete_state_snapshot(ccx, *snapshotId)
894    }
895}
896
897impl Cheatcode for deleteStateSnapshotCall {
898    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
899        let Self { snapshotId } = self;
900        inner_delete_state_snapshot(ccx, *snapshotId)
901    }
902}
903
904// Deprecated in favor of `deleteStateSnapshotsCall`
905impl Cheatcode for deleteSnapshotsCall {
906    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
907        let Self {} = self;
908        inner_delete_state_snapshots(ccx)
909    }
910}
911
912impl Cheatcode for deleteStateSnapshotsCall {
913    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
914        let Self {} = self;
915        inner_delete_state_snapshots(ccx)
916    }
917}
918
919impl Cheatcode for startStateDiffRecordingCall {
920    fn apply(&self, state: &mut Cheatcodes) -> Result {
921        let Self {} = self;
922        state.recorded_account_diffs_stack = Some(Default::default());
923        // Enable mapping recording to track mapping slot accesses
924        state.mapping_slots.get_or_insert_default();
925        Ok(Default::default())
926    }
927}
928
929impl Cheatcode for stopAndReturnStateDiffCall {
930    fn apply(&self, state: &mut Cheatcodes) -> Result {
931        let Self {} = self;
932        get_state_diff(state)
933    }
934}
935
936impl Cheatcode for getStateDiffCall {
937    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
938        let mut diffs = String::new();
939        let state_diffs = get_recorded_state_diffs(ccx);
940        for (address, state_diffs) in state_diffs {
941            diffs.push_str(&format!("{address}\n"));
942            diffs.push_str(&format!("{state_diffs}\n"));
943        }
944        Ok(diffs.abi_encode())
945    }
946}
947
948impl Cheatcode for getStateDiffJsonCall {
949    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
950        let state_diffs = get_recorded_state_diffs(ccx);
951        Ok(serde_json::to_string(&state_diffs)?.abi_encode())
952    }
953}
954
955impl Cheatcode for getStorageSlotsCall {
956    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
957        let Self { target, variableName } = self;
958
959        let storage_layout = get_contract_data(ccx, *target)
960            .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
961            .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
962
963        trace!(storage = ?storage_layout.storage, "fetched storage");
964
965        let variable_name_lower = variableName.to_lowercase();
966        let storage = storage_layout
967            .storage
968            .iter()
969            .find(|s| s.label.to_lowercase() == variable_name_lower)
970            .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
971
972        let storage_type = storage_layout
973            .types
974            .get(&storage.storage_type)
975            .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
976
977        if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
978        {
979            return Err(fmt_err!(
980                "cannot get storage slots for variables with mapping or dynamic array types"
981            ));
982        }
983
984        let slot = U256::from_str(&storage.slot).map_err(|_| {
985            fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
986        })?;
987
988        let mut slots = Vec::new();
989
990        // Always push the base slot
991        slots.push(slot);
992
993        if storage_type.encoding == ENCODING_INPLACE {
994            // For inplace encoding, calculate the number of slots needed
995            let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
996                fmt_err!(
997                    "invalid number_of_bytes {} for variable {variableName}",
998                    storage_type.number_of_bytes
999                )
1000            })?;
1001            let num_slots = num_bytes.div_ceil(U256::from(32));
1002
1003            // Start from 1 since base slot is already added
1004            for i in 1..num_slots.to::<usize>() {
1005                slots.push(slot + U256::from(i));
1006            }
1007        }
1008
1009        if storage_type.encoding == ENCODING_BYTES {
1010            // Try to check if it's a long bytes/string by reading the current storage
1011            // value
1012            let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1013            if let Ok(value) = journal.sload(db, *target, slot, false) {
1014                let value_bytes = value.data.to_be_bytes::<32>();
1015                let length_byte = value_bytes[31];
1016                // Check if it's a long bytes/string (LSB is 1)
1017                if length_byte & 1 == 1 {
1018                    // Calculate data slots for long bytes/string
1019                    let length: U256 = value.data >> 1;
1020                    let num_data_slots = length.to::<usize>().div_ceil(32);
1021                    let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
1022
1023                    for i in 0..num_data_slots {
1024                        slots.push(data_start + U256::from(i));
1025                    }
1026                }
1027            }
1028        }
1029
1030        Ok(slots.abi_encode())
1031    }
1032}
1033
1034impl Cheatcode for getStorageAccessesCall {
1035    fn apply(&self, state: &mut Cheatcodes) -> Result {
1036        let mut storage_accesses = Vec::new();
1037
1038        if let Some(recorded_diffs) = &state.recorded_account_diffs_stack {
1039            for account_accesses in recorded_diffs.iter().flatten() {
1040                storage_accesses.extend(account_accesses.storageAccesses.clone());
1041            }
1042        }
1043
1044        Ok(storage_accesses.abi_encode())
1045    }
1046}
1047
1048impl Cheatcode for broadcastRawTransactionCall {
1049    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1050        let tx = TxEnvelope::decode(&mut self.data.as_ref())
1051            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1052
1053        let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
1054        db.transact_from_tx(
1055            &tx.clone().into(),
1056            env.to_owned(),
1057            journal,
1058            &mut *executor.get_inspector(ccx.state),
1059        )?;
1060
1061        if ccx.state.broadcast.is_some() {
1062            ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1063                rpc: ccx.ecx.journaled_state.database.active_fork_url(),
1064                transaction: tx.try_into()?,
1065            });
1066        }
1067
1068        Ok(Default::default())
1069    }
1070}
1071
1072impl Cheatcode for setBlockhashCall {
1073    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1074        let Self { blockNumber, blockHash } = *self;
1075        ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1076        ensure!(
1077            blockNumber <= U256::from(ccx.ecx.block().number),
1078            "block number must be less than or equal to the current block number"
1079        );
1080
1081        ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash);
1082
1083        Ok(Default::default())
1084    }
1085}
1086
1087impl Cheatcode for executeTransactionCall {
1088    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1089        use crate::env::FORGE_CONTEXT;
1090
1091        // Block in script contexts.
1092        if let Some(ctx) = FORGE_CONTEXT.get()
1093            && *ctx == ForgeContext::ScriptGroup
1094        {
1095            return Err(fmt_err!("executeTransaction is not allowed in forge script"));
1096        }
1097
1098        // Decode the RLP-encoded signed transaction.
1099        let tx = FoundryTxEnvelope::decode(&mut self.rawTx.as_ref())
1100            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1101
1102        // Reject unsupported transaction types.
1103        // TODO: add support for OP deposit transactions.
1104        if matches!(tx, FoundryTxEnvelope::Deposit(_)) {
1105            return Err(fmt_err!(
1106                "OP deposit transactions are not yet supported by executeTransaction"
1107            ));
1108        }
1109        // TODO: add support for Tempo AA transactions.
1110        if matches!(tx, FoundryTxEnvelope::Tempo(_)) {
1111            return Err(fmt_err!("Tempo transactions are not yet supported by executeTransaction"));
1112        }
1113
1114        // Recover signer from the transaction signature.
1115        let sender = tx.recover().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1116
1117        // Build TxEnv from the recovered transaction.
1118        let tx_env = <TxEnv as FromRecoveredTx<FoundryTxEnvelope>>::from_recovered_tx(&tx, sender);
1119
1120        // Mark as inner context so isolation mode doesn't trigger a nested transact_inner
1121        // when the inner EVM executes calls at depth == 1.
1122        executor.set_in_inner_context(true, Some(sender));
1123
1124        let res = {
1125            let mut inspector = executor.get_inspector(ccx.state);
1126
1127            let res = {
1128                let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
1129                let cached_env =
1130                    foundry_evm_core::Env::from(env.cfg.clone(), env.block.clone(), env.tx.clone());
1131
1132                // Override env for isolated execution.
1133                env.block.basefee = 0;
1134                *env.tx = tx_env;
1135                env.tx.gas_price = 0;
1136                env.tx.gas_priority_fee = None;
1137
1138                // Enable nonce checks for realistic simulation.
1139                env.cfg.disable_nonce_check = false;
1140
1141                // EIP-3860: enforce initcode size limit.
1142                env.cfg.limit_contract_initcode_size =
1143                    Some(revm::primitives::eip3860::MAX_INITCODE_SIZE);
1144
1145                // Create a new EVM instance with the inspector.
1146                let mut evm = new_evm_with_inspector(db, env.to_owned(), &mut *inspector);
1147
1148                // Clone journaled state and mark all accounts/slots cold.
1149                evm.journaled_state.state = {
1150                    let mut state = journal.state.clone();
1151                    for (addr, acc_mut) in &mut state {
1152                        if journal.warm_addresses.is_cold(addr) {
1153                            acc_mut.mark_cold();
1154                        }
1155                        for slot_mut in acc_mut.storage.values_mut() {
1156                            slot_mut.is_cold = true;
1157                            slot_mut.original_value = slot_mut.present_value;
1158                        }
1159                    }
1160                    state
1161                };
1162
1163                // Set depth to 1 for proper trace collection.
1164                evm.journaled_state.depth = 1;
1165
1166                let res = evm.transact(env.tx.clone());
1167
1168                // Restore the original environment.
1169                *env.tx = cached_env.tx;
1170                *env.cfg = cached_env.evm_env.cfg_env;
1171                env.block.basefee = cached_env.evm_env.block_env.basefee;
1172
1173                res
1174            };
1175
1176            // Inspector must be dropped before we can call set_in_inner_context again.
1177            drop(inspector);
1178            res
1179        };
1180
1181        // Reset inner context flag.
1182        executor.set_in_inner_context(false, None);
1183
1184        let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1185
1186        // Merge state changes back into the parent journaled state.
1187        for (addr, mut acc) in res.state {
1188            let Some(acc_mut) = ccx.ecx.journaled_state.state.get_mut(&addr) else {
1189                ccx.ecx.journaled_state.state.insert(addr, acc);
1190                continue;
1191            };
1192
1193            // Preserve warm account status from parent context.
1194            if acc.status.contains(AccountStatus::Cold)
1195                && !acc_mut.status.contains(AccountStatus::Cold)
1196            {
1197                acc.status -= AccountStatus::Cold;
1198            }
1199            acc_mut.info = acc.info;
1200            acc_mut.status |= acc.status;
1201
1202            // Merge storage changes.
1203            for (key, val) in acc.storage {
1204                let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
1205                    acc_mut.storage.insert(key, val);
1206                    continue;
1207                };
1208                slot_mut.present_value = val.present_value;
1209                slot_mut.is_cold &= val.is_cold;
1210            }
1211        }
1212
1213        // Return output bytes.
1214        let output = match res.result {
1215            ExecutionResult::Success { output, .. } => output.into_data(),
1216            ExecutionResult::Halt { reason, .. } => {
1217                return Err(fmt_err!("transaction halted: {reason:?}"));
1218            }
1219            ExecutionResult::Revert { output, .. } => {
1220                return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1221            }
1222        };
1223
1224        Ok(output.abi_encode())
1225    }
1226}
1227
1228impl Cheatcode for startDebugTraceRecordingCall {
1229    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1230        let Some(tracer) = executor.tracing_inspector() else {
1231            return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1232        };
1233
1234        let mut info = RecordDebugStepInfo {
1235            // will be updated later
1236            start_node_idx: 0,
1237            // keep the original config to revert back later
1238            original_tracer_config: *tracer.config(),
1239        };
1240
1241        // turn on tracer debug configuration for recording
1242        *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None");
1243
1244        // track where the recording starts
1245        if let Some(last_node) = tracer.traces().nodes().last() {
1246            info.start_node_idx = last_node.idx;
1247        }
1248
1249        ccx.state.record_debug_steps_info = Some(info);
1250        Ok(Default::default())
1251    }
1252}
1253
1254impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1255    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1256        let Some(tracer) = executor.tracing_inspector() else {
1257            return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1258        };
1259
1260        let Some(record_info) = ccx.state.record_debug_steps_info else {
1261            return Err(Error::from("nothing recorded"));
1262        };
1263
1264        // Use the trace nodes to flatten the call trace
1265        let root = tracer.traces();
1266        let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1267
1268        let debug_steps: Vec<DebugStep> =
1269            steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1270        // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage.
1271        if !record_info.original_tracer_config.record_steps {
1272            tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1273                node.trace.steps = Vec::new();
1274                node.logs = Vec::new();
1275                node.ordering = Vec::new();
1276            });
1277        }
1278
1279        // Revert the tracer config to the one before recording
1280        tracer.update_config(|_config| record_info.original_tracer_config);
1281
1282        // Clean up the recording info
1283        ccx.state.record_debug_steps_info = None;
1284
1285        Ok(debug_steps.abi_encode())
1286    }
1287}
1288
1289impl Cheatcode for setEvmVersionCall {
1290    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1291        let Self { evm } = self;
1292        let spec_id = evm_spec_id(
1293            EvmVersion::from_str(evm)
1294                .map_err(|_| Error::from(format!("invalid evm version {evm}")))?,
1295        );
1296        ccx.state.execution_evm_version = Some(spec_id);
1297        Ok(Default::default())
1298    }
1299}
1300
1301impl Cheatcode for getEvmVersionCall {
1302    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1303        Ok(ccx.ecx.cfg().spec.to_string().to_lowercase().abi_encode())
1304    }
1305}
1306
1307pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result {
1308    let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1309    let account = journal.load_account(db, *address)?;
1310    Ok(account.info.nonce.abi_encode())
1311}
1312
1313fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result {
1314    let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1315    Ok(db.snapshot_state(journal, &mut env).abi_encode())
1316}
1317
1318fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1319    let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1320    let result = if let Some(journaled_state) =
1321        db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep)
1322    {
1323        // we reset the evm's journaled_state to the state of the snapshot previous state
1324        ccx.ecx.journaled_state.inner = journaled_state;
1325        true
1326    } else {
1327        false
1328    };
1329    Ok(result.abi_encode())
1330}
1331
1332fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1333    let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1334
1335    let result = if let Some(journaled_state) =
1336        db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove)
1337    {
1338        // we reset the evm's journaled_state to the state of the snapshot previous state
1339        ccx.ecx.journaled_state.inner = journaled_state;
1340        true
1341    } else {
1342        false
1343    };
1344    Ok(result.abi_encode())
1345}
1346
1347fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1348    let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id);
1349    Ok(result.abi_encode())
1350}
1351
1352fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result {
1353    ccx.ecx.journaled_state.database.delete_state_snapshots();
1354    Ok(Default::default())
1355}
1356
1357fn inner_value_snapshot(
1358    ccx: &mut CheatsCtxt,
1359    group: Option<String>,
1360    name: Option<String>,
1361    value: String,
1362) -> Result {
1363    let (group, name) = derive_snapshot_name(ccx, group, name);
1364
1365    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1366
1367    Ok(Default::default())
1368}
1369
1370fn inner_last_gas_snapshot(
1371    ccx: &mut CheatsCtxt,
1372    group: Option<String>,
1373    name: Option<String>,
1374    value: u64,
1375) -> Result {
1376    let (group, name) = derive_snapshot_name(ccx, group, name);
1377
1378    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1379
1380    Ok(value.abi_encode())
1381}
1382
1383fn inner_start_gas_snapshot(
1384    ccx: &mut CheatsCtxt,
1385    group: Option<String>,
1386    name: Option<String>,
1387) -> Result {
1388    // Revert if there is an active gas snapshot as we can only have one active snapshot at a time.
1389    if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1390        bail!("gas snapshot was already started with group: {group} and name: {name}");
1391    }
1392
1393    let (group, name) = derive_snapshot_name(ccx, group, name);
1394
1395    ccx.state.gas_metering.gas_records.push(GasRecord {
1396        group: group.clone(),
1397        name: name.clone(),
1398        gas_used: 0,
1399        depth: ccx.ecx.journaled_state.depth(),
1400    });
1401
1402    ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1403
1404    ccx.state.gas_metering.start();
1405
1406    Ok(Default::default())
1407}
1408
1409fn inner_stop_gas_snapshot(
1410    ccx: &mut CheatsCtxt,
1411    group: Option<String>,
1412    name: Option<String>,
1413) -> Result {
1414    // If group and name are not provided, use the last snapshot group and name.
1415    let (group, name) = group
1416        .zip(name)
1417        .unwrap_or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone().unwrap());
1418
1419    if let Some(record) = ccx
1420        .state
1421        .gas_metering
1422        .gas_records
1423        .iter_mut()
1424        .find(|record| record.group == group && record.name == name)
1425    {
1426        // Calculate the gas used since the snapshot was started.
1427        // We subtract 171 from the gas used to account for gas used by the snapshot itself.
1428        let value = record.gas_used.saturating_sub(171);
1429
1430        ccx.state
1431            .gas_snapshots
1432            .entry(group.clone())
1433            .or_default()
1434            .insert(name.clone(), value.to_string());
1435
1436        // Stop the gas metering.
1437        ccx.state.gas_metering.stop();
1438
1439        // Remove the gas record.
1440        ccx.state
1441            .gas_metering
1442            .gas_records
1443            .retain(|record| record.group != group && record.name != name);
1444
1445        // Clear last snapshot cache if we have an exact match.
1446        if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1447            && snapshot_group == &group
1448            && snapshot_name == &name
1449        {
1450            ccx.state.gas_metering.active_gas_snapshot = None;
1451        }
1452
1453        Ok(value.abi_encode())
1454    } else {
1455        bail!("no gas snapshot was started with the name: {name} in group: {group}");
1456    }
1457}
1458
1459// Derives the snapshot group and name from the provided group and name or the running contract.
1460fn derive_snapshot_name(
1461    ccx: &CheatsCtxt,
1462    group: Option<String>,
1463    name: Option<String>,
1464) -> (String, String) {
1465    let group = group.unwrap_or_else(|| {
1466        ccx.state.config.running_artifact.clone().expect("expected running contract").name
1467    });
1468    let name = name.unwrap_or_else(|| "default".to_string());
1469    (group, name)
1470}
1471
1472/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and
1473/// `tx.origin`.
1474///
1475/// Depending on the current caller mode, one of the following results will be returned:
1476/// - If there is an active prank:
1477///     - caller_mode will be equal to:
1478///         - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`.
1479///         - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`.
1480///     - `msg.sender` will be equal to the address set for the prank.
1481///     - `tx.origin` will be equal to the default sender address unless an alternative one has been
1482///       set when configuring the prank.
1483///
1484/// - If there is an active broadcast:
1485///     - caller_mode will be equal to:
1486///         - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`.
1487///         - [CallerMode::RecurrentBroadcast] if the broadcast has been set with
1488///           `vm.startBroadcast(..)`.
1489///     - `msg.sender` and `tx.origin` will be equal to the address provided when setting the
1490///       broadcast.
1491///
1492/// - If no caller modification is active:
1493///     - caller_mode will be equal to [CallerMode::None],
1494///     - `msg.sender` and `tx.origin` will be equal to the default sender address.
1495fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result {
1496    let mut mode = CallerMode::None;
1497    let mut new_caller = default_sender;
1498    let mut new_origin = default_sender;
1499    if let Some(prank) = state.get_prank(call_depth) {
1500        mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1501        new_caller = &prank.new_caller;
1502        if let Some(new) = &prank.new_origin {
1503            new_origin = new;
1504        }
1505    } else if let Some(broadcast) = &state.broadcast {
1506        mode = if broadcast.single_call {
1507            CallerMode::Broadcast
1508        } else {
1509            CallerMode::RecurrentBroadcast
1510        };
1511        new_caller = &broadcast.new_origin;
1512        new_origin = &broadcast.new_origin;
1513    }
1514
1515    Ok((mode, new_caller, new_origin).abi_encode_params())
1516}
1517
1518/// Ensures the `Account` is loaded and touched.
1519pub(super) fn journaled_account<'a>(
1520    ecx: Ecx<'a, '_, '_>,
1521    addr: Address,
1522) -> Result<&'a mut Account> {
1523    ensure_loaded_account(ecx, addr)?;
1524    Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded"))
1525}
1526
1527pub(super) fn ensure_loaded_account(ecx: Ecx, addr: Address) -> Result<()> {
1528    let (db, journal, _) = ecx.as_db_env_and_journal();
1529    journal.load_account(db, addr)?;
1530    journal.touch(addr);
1531    Ok(())
1532}
1533
1534/// Consumes recorded account accesses and returns them as an abi encoded
1535/// array of [AccountAccess]. If there are no accounts were
1536/// recorded as accessed, an abi encoded empty array is returned.
1537///
1538/// In the case where `stopAndReturnStateDiff` is called at a lower
1539/// depth than `startStateDiffRecording`, multiple `Vec<RecordedAccountAccesses>`
1540/// will be flattened, preserving the order of the accesses.
1541fn get_state_diff(state: &mut Cheatcodes) -> Result {
1542    let res = state
1543        .recorded_account_diffs_stack
1544        .replace(Default::default())
1545        .unwrap_or_default()
1546        .into_iter()
1547        .flatten()
1548        .collect::<Vec<_>>();
1549    Ok(res.abi_encode())
1550}
1551
1552/// Helper function that creates a `GenesisAccount` from a regular `Account`.
1553fn genesis_account(account: &Account) -> GenesisAccount {
1554    GenesisAccount {
1555        nonce: Some(account.info.nonce),
1556        balance: account.info.balance,
1557        code: account.info.code.as_ref().map(|o| o.original_bytes()),
1558        storage: Some(
1559            account
1560                .storage
1561                .iter()
1562                .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1563                .collect(),
1564        ),
1565        private_key: None,
1566    }
1567}
1568
1569/// Helper function to returns state diffs recorded for each changed account.
1570fn get_recorded_state_diffs(ccx: &mut CheatsCtxt) -> BTreeMap<Address, AccountStateDiffs> {
1571    let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1572
1573    // First, collect all unique addresses we need to look up
1574    let mut addresses_to_lookup = HashSet::new();
1575    if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1576        for account_access in records.iter().flatten() {
1577            if !account_access.storageAccesses.is_empty()
1578                || account_access.oldBalance != account_access.newBalance
1579            {
1580                addresses_to_lookup.insert(account_access.account);
1581                for storage_access in &account_access.storageAccesses {
1582                    if storage_access.isWrite && !storage_access.reverted {
1583                        addresses_to_lookup.insert(storage_access.account);
1584                    }
1585                }
1586            }
1587        }
1588    }
1589
1590    // Look up contract names and storage layouts for all addresses
1591    let mut contract_names = HashMap::new();
1592    let mut storage_layouts = HashMap::new();
1593    for address in addresses_to_lookup {
1594        if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) {
1595            contract_names.insert(address, artifact_id.identifier());
1596
1597            // Also get storage layout if available
1598            if let Some(storage_layout) = &contract_data.storage_layout {
1599                storage_layouts.insert(address, storage_layout.clone());
1600            }
1601        }
1602    }
1603
1604    // Now process the records
1605    if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1606        records
1607            .iter()
1608            .flatten()
1609            .filter(|account_access| {
1610                !account_access.storageAccesses.is_empty()
1611                    || account_access.oldBalance != account_access.newBalance
1612                    || account_access.oldNonce != account_access.newNonce
1613            })
1614            .for_each(|account_access| {
1615                // Record account balance diffs.
1616                if account_access.oldBalance != account_access.newBalance {
1617                    let account_diff =
1618                        state_diffs.entry(account_access.account).or_insert_with(|| {
1619                            AccountStateDiffs {
1620                                label: ccx.state.labels.get(&account_access.account).cloned(),
1621                                contract: contract_names.get(&account_access.account).cloned(),
1622                                ..Default::default()
1623                            }
1624                        });
1625                    // Update balance diff. Do not overwrite the initial balance if already set.
1626                    if let Some(diff) = &mut account_diff.balance_diff {
1627                        diff.new_value = account_access.newBalance;
1628                    } else {
1629                        account_diff.balance_diff = Some(BalanceDiff {
1630                            previous_value: account_access.oldBalance,
1631                            new_value: account_access.newBalance,
1632                        });
1633                    }
1634                }
1635
1636                // Record account nonce diffs.
1637                if account_access.oldNonce != account_access.newNonce {
1638                    let account_diff =
1639                        state_diffs.entry(account_access.account).or_insert_with(|| {
1640                            AccountStateDiffs {
1641                                label: ccx.state.labels.get(&account_access.account).cloned(),
1642                                contract: contract_names.get(&account_access.account).cloned(),
1643                                ..Default::default()
1644                            }
1645                        });
1646                    // Update nonce diff. Do not overwrite the initial nonce if already set.
1647                    if let Some(diff) = &mut account_diff.nonce_diff {
1648                        diff.new_value = account_access.newNonce;
1649                    } else {
1650                        account_diff.nonce_diff = Some(NonceDiff {
1651                            previous_value: account_access.oldNonce,
1652                            new_value: account_access.newNonce,
1653                        });
1654                    }
1655                }
1656
1657                // Collect all storage accesses for this account
1658                let raw_changes_by_slot = account_access
1659                    .storageAccesses
1660                    .iter()
1661                    .filter_map(|access| {
1662                        (access.isWrite && !access.reverted)
1663                            .then_some((access.slot, (access.previousValue, access.newValue)))
1664                    })
1665                    .collect::<BTreeMap<_, _>>();
1666
1667                // Record account state diffs.
1668                for storage_access in &account_access.storageAccesses {
1669                    if storage_access.isWrite && !storage_access.reverted {
1670                        let account_diff = state_diffs
1671                            .entry(storage_access.account)
1672                            .or_insert_with(|| AccountStateDiffs {
1673                                label: ccx.state.labels.get(&storage_access.account).cloned(),
1674                                contract: contract_names.get(&storage_access.account).cloned(),
1675                                ..Default::default()
1676                            });
1677                        let layout = storage_layouts.get(&storage_access.account);
1678                        // Update state diff. Do not overwrite the initial value if already set.
1679                        let entry = match account_diff.state_diff.entry(storage_access.slot) {
1680                            Entry::Vacant(slot_state_diff) => {
1681                                // Get storage layout info for this slot
1682                                // Include mapping slots if available for the account
1683                                let mapping_slots = ccx
1684                                    .state
1685                                    .mapping_slots
1686                                    .as_ref()
1687                                    .and_then(|slots| slots.get(&storage_access.account));
1688
1689                                let slot_info = layout.and_then(|layout| {
1690                                    let decoder = SlotIdentifier::new(layout.clone());
1691                                    decoder.identify(&storage_access.slot, mapping_slots).or_else(
1692                                        || {
1693                                            // Create a map of new values for bytes/string
1694                                            // identification. These values are used to determine
1695                                            // the length of the data which helps determine how many
1696                                            // slots to search
1697                                            let current_base_slot_values = raw_changes_by_slot
1698                                                .iter()
1699                                                .map(|(slot, (_, new_val))| (*slot, *new_val))
1700                                                .collect::<B256Map<_>>();
1701                                            decoder.identify_bytes_or_string(
1702                                                &storage_access.slot,
1703                                                &current_base_slot_values,
1704                                            )
1705                                        },
1706                                    )
1707                                });
1708
1709                                slot_state_diff.insert(SlotStateDiff {
1710                                    previous_value: storage_access.previousValue,
1711                                    new_value: storage_access.newValue,
1712                                    slot_info,
1713                                })
1714                            }
1715                            Entry::Occupied(slot_state_diff) => {
1716                                let entry = slot_state_diff.into_mut();
1717                                entry.new_value = storage_access.newValue;
1718                                entry
1719                            }
1720                        };
1721
1722                        // Update decoded values if we have slot info
1723                        if let Some(slot_info) = &mut entry.slot_info {
1724                            slot_info.decode_values(entry.previous_value, storage_access.newValue);
1725                            if slot_info.is_bytes_or_string() {
1726                                slot_info.decode_bytes_or_string_values(
1727                                    &storage_access.slot,
1728                                    &raw_changes_by_slot,
1729                                );
1730                            }
1731                        }
1732                    }
1733                }
1734            });
1735    }
1736    state_diffs
1737}
1738
1739/// EIP-1967 implementation storage slot
1740const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
1741
1742/// EIP-1822 UUPS implementation storage slot: keccak256("PROXIABLE")
1743const EIP1822_PROXIABLE_SLOT: &str =
1744    "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
1745
1746/// Helper function to get the contract data from the deployed code at an address.
1747fn get_contract_data<'a>(
1748    ccx: &'a mut CheatsCtxt,
1749    address: Address,
1750) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
1751    // Check if we have available artifacts to match against
1752    let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1753
1754    // Try to load the account and get its code
1755    let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1756    let account = journal.load_account(db, address).ok()?;
1757    let code = account.info.code.as_ref()?;
1758
1759    // Skip if code is empty
1760    if code.is_empty() {
1761        return None;
1762    }
1763
1764    // Try to find the artifact by deployed code
1765    let code_bytes = code.original_bytes();
1766    // First check for proxy patterns
1767    let hex_str = hex::encode(&code_bytes);
1768    let find_by_suffix =
1769        |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
1770    // Simple proxy detection based on storage slot patterns
1771    if hex_str.contains(EIP1967_IMPL_SLOT)
1772        && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
1773    {
1774        return Some(result);
1775    } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
1776        && let Some(result) = find_by_suffix(":UUPSUpgradeable")
1777    {
1778        return Some(result);
1779    }
1780
1781    // Try exact match
1782    if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1783        return Some(result);
1784    }
1785
1786    // Fallback to fuzzy matching if exact match fails
1787    artifacts.find_by_deployed_code(&code_bytes)
1788}
1789
1790/// Helper function to set / unset cold storage slot of the target address.
1791fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) {
1792    if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target)
1793        && let Some(storage_slot) = account.storage.get_mut(&slot)
1794    {
1795        storage_slot.is_cold = cold;
1796    }
1797}