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