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