Skip to main content

foundry_cheatcodes/
evm.rs

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