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