Skip to main content

foundry_cheatcodes/
evm.rs

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