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