Skip to main content

foundry_cheatcodes/
evm.rs

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