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