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