Skip to main content

foundry_cheatcodes/
inspector.rs

1//! Cheatcode EVM inspector.
2
3use crate::{
4    Cheatcode, CheatsConfig, CheatsCtxt, Error, Result,
5    Vm::{self, AccountAccess},
6    evm::{
7        DealRecord, GasRecord, RecordAccess, journaled_account,
8        mock::{MockCallDataContext, MockCallReturnData},
9        prank::Prank,
10    },
11    inspector::utils::CommonCreateInput,
12    script::{Broadcast, Wallets},
13    test::{
14        assume::AssumeNoRevert,
15        expect::{
16            self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedCreate,
17            ExpectedEmitTracker, ExpectedRevert, ExpectedRevertKind,
18        },
19        revert_handlers,
20    },
21    utils::IgnoredTraces,
22};
23use alloy_consensus::BlobTransactionSidecarVariant;
24use alloy_network::{Ethereum, Network, TransactionBuilder};
25use alloy_primitives::{
26    Address, B256, Bytes, Log, TxKind, U256, hex,
27    map::{AddressHashMap, HashMap, HashSet},
28};
29use alloy_rpc_types::AccessList;
30use alloy_signer_local::PrivateKeySigner;
31use alloy_sol_types::{SolCall, SolInterface, SolValue};
32use foundry_common::{
33    FoundryTransactionBuilder, SELECTOR_LEN, TransactionMaybeSigned,
34    mapping_slots::{
35        MappingSlots, PendingMappingHash, capture_hash as capture_mapping_hash,
36        record_hash as record_mapping_hash, step as mapping_step,
37    },
38};
39use foundry_evm_core::{
40    Breakpoints, EvmEnv, FoundryTransaction, InspectorExt,
41    abi::Vm::stopExpectSafeMemoryCall,
42    backend::{ContextUpdateFor, DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic},
43    constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME},
44    env::FoundryContextExt,
45    evm::{
46        BlockEnvFor, ChainFor, EthEvmNetwork, EvmFactoryFor, FoundryContextFor, FoundryEvmFactory,
47        FoundryEvmNetwork, NestedEvmClosureFor, SpecFor, TransactionRequestFor, TxEnvFor,
48        with_inherited_evm,
49    },
50};
51use foundry_evm_traces::{
52    TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier,
53};
54use foundry_wallets::wallet_multi::MultiWallet;
55use itertools::Itertools;
56use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
57use rand::Rng;
58use revm::{
59    Inspector, JournalEntry,
60    bytecode::opcode as op,
61    context::{Cfg, ContextTr, Host, JournalTr, Transaction, TransactionType, result::EVMError},
62    context_interface::{CreateScheme, transaction::SignedAuthorization},
63    handler::FrameResult,
64    interpreter::{
65        CallInput, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome,
66        FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
67        interpreter_types::{Jumps, LoopControl, MemoryTr, ReturnData},
68        return_ok,
69    },
70};
71use serde_json::Value;
72use std::{
73    cmp::max,
74    collections::{BTreeMap, VecDeque},
75    fmt::Debug,
76    fs::File,
77    io::BufReader,
78    ops::Range,
79    path::PathBuf,
80    sync::{Arc, OnceLock},
81};
82
83mod utils;
84
85pub mod analysis;
86pub use analysis::CheatcodeAnalysis;
87
88/// Helper trait for running nested EVM operations from inside cheatcode implementations.
89pub trait CheatcodesExecutor<FEN: FoundryEvmNetwork> {
90    /// Runs a closure with a nested EVM built from the current context.
91    /// The inspector is assembled internally — never exposed to the caller.
92    fn with_nested_evm(
93        &mut self,
94        cheats: &mut Cheatcodes<FEN>,
95        ecx: &mut FoundryContextFor<'_, FEN>,
96        f: NestedEvmClosureFor<'_, FEN>,
97    ) -> Result<(), EVMError<DatabaseError>>;
98
99    /// Replays a historical transaction on the database. Inspector is assembled internally.
100    fn transact_on_db(
101        &mut self,
102        cheats: &mut Cheatcodes<FEN>,
103        ecx: &mut FoundryContextFor<'_, FEN>,
104        fork_id: Option<U256>,
105        transaction: B256,
106    ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>>;
107
108    /// Executes a `TransactionRequest` on the database. Inspector is assembled internally.
109    fn transact_from_tx_on_db(
110        &mut self,
111        cheats: &mut Cheatcodes<FEN>,
112        ecx: &mut FoundryContextFor<'_, FEN>,
113        tx: TxEnvFor<FEN>,
114    ) -> eyre::Result<()>;
115
116    /// Runs a closure with a fresh nested EVM built from a raw database and environment.
117    /// Unlike `with_nested_evm`, this does NOT clone from `ecx` and does NOT write back.
118    /// The caller is responsible for state merging. Used by `executeTransactionCall`.
119    /// Returns the final EVM environment after the closure runs (consumed without cloning).
120    #[allow(clippy::type_complexity)]
121    fn with_fresh_nested_evm(
122        &mut self,
123        cheats: &mut Cheatcodes<FEN>,
124        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
125        evm_env: EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
126        chain_context: ChainFor<FEN>,
127        f: NestedEvmClosureFor<'_, FEN>,
128    ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>>;
129
130    /// Simulates `console.log` invocation.
131    fn console_log(&mut self, msg: &str);
132
133    /// Returns a mutable reference to the tracing inspector if it is available.
134    fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
135        None
136    }
137
138    /// Marks that the next EVM frame is an "inner context" so that isolation mode does not
139    /// trigger a nested `transact_inner`. `original_origin` is stored for the existing
140    /// inner-context adjustment logic that restores `tx.origin`.
141    fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option<Address>) {}
142}
143
144/// Builds a sub-EVM from the current context and executes the given CREATE frame.
145pub(crate) fn exec_create<FEN: FoundryEvmNetwork>(
146    executor: &mut dyn CheatcodesExecutor<FEN>,
147    inputs: CreateInputs,
148    ccx: &mut CheatsCtxt<'_, '_, FEN>,
149) -> std::result::Result<CreateOutcome, EVMError<DatabaseError>> {
150    let fee_token = ccx.ecx.tx().fee_token();
151    let tx_origin = ccx.ecx.tx().caller();
152    let mut inputs = Some(inputs);
153    let mut outcome = None;
154    executor.with_nested_evm(ccx.state, ccx.ecx, &mut |evm| {
155        evm.tx_mut().set_fee_token(fee_token);
156        evm.tx_mut().set_caller(tx_origin);
157        let inputs = inputs.take().unwrap();
158        evm.journal_inner_mut().depth += 1;
159
160        let frame = FrameInput::Create(Box::new(inputs));
161
162        let result = match evm.run_execution(frame)? {
163            FrameResult::Call(_) => unreachable!(),
164            FrameResult::Create(create) => create,
165        };
166
167        evm.journal_inner_mut().depth -= 1;
168
169        outcome = Some(result);
170        Ok(())
171    })?;
172    Ok(outcome.unwrap())
173}
174
175/// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an
176/// inspector.
177#[derive(Debug, Default, Clone, Copy)]
178struct TransparentCheatcodesExecutor;
179
180impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for TransparentCheatcodesExecutor {
181    fn with_nested_evm(
182        &mut self,
183        cheats: &mut Cheatcodes<FEN>,
184        ecx: &mut FoundryContextFor<'_, FEN>,
185        f: NestedEvmClosureFor<'_, FEN>,
186    ) -> Result<(), EVMError<DatabaseError>> {
187        with_inherited_evm::<FEN::EvmFactory, _>(ecx, cheats, f)
188    }
189
190    fn with_fresh_nested_evm(
191        &mut self,
192        cheats: &mut Cheatcodes<FEN>,
193        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
194        evm_env: EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
195        chain_context: ChainFor<FEN>,
196        f: NestedEvmClosureFor<'_, FEN>,
197    ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>> {
198        let mut evm =
199            FEN::EvmFactory::default().create_nested_evm_with_inspector(db, evm_env, cheats);
200        *evm.chain_mut() = chain_context;
201        f(&mut *evm)?;
202        Ok(evm.to_evm_env())
203    }
204
205    fn transact_on_db(
206        &mut self,
207        cheats: &mut Cheatcodes<FEN>,
208        ecx: &mut FoundryContextFor<'_, FEN>,
209        fork_id: Option<U256>,
210        transaction: B256,
211    ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>> {
212        let evm_env = ecx.evm_clone();
213        let outer_tx_env = ecx.tx_clone();
214        let (db, inner) = ecx.db_journal_inner_mut();
215        db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, cheats)
216    }
217
218    fn transact_from_tx_on_db(
219        &mut self,
220        cheats: &mut Cheatcodes<FEN>,
221        ecx: &mut FoundryContextFor<'_, FEN>,
222        tx: TxEnvFor<FEN>,
223    ) -> eyre::Result<()> {
224        let evm_env = ecx.evm_clone();
225        let (db, inner) = ecx.db_journal_inner_mut();
226        db.transact_from_tx(tx, evm_env, inner, cheats)
227    }
228
229    fn console_log(&mut self, _msg: &str) {}
230}
231
232macro_rules! try_or_return {
233    ($e:expr) => {
234        match $e {
235            Ok(v) => v,
236            Err(_) => return,
237        }
238    };
239}
240
241/// Contains additional, test specific resources that should be kept for the duration of the test
242#[derive(Debug, Default)]
243pub struct TestContext {
244    /// Buffered readers for files opened for reading (path => BufReader mapping)
245    pub opened_read_files: HashMap<PathBuf, BufReader<File>>,
246}
247
248/// Every time we clone `Context`, we want it to be empty
249impl Clone for TestContext {
250    fn clone(&self) -> Self {
251        Default::default()
252    }
253}
254
255impl TestContext {
256    /// Clears the context.
257    pub fn clear(&mut self) {
258        self.opened_read_files.clear();
259    }
260}
261
262/// Helps collecting transactions from different forks.
263#[derive(Clone, Debug)]
264pub struct BroadcastableTransaction<N: Network = Ethereum> {
265    /// The optional RPC URL.
266    pub rpc: Option<String>,
267    /// The transaction to broadcast.
268    pub transaction: TransactionMaybeSigned<N>,
269}
270
271#[derive(Clone, Debug, Copy)]
272pub struct RecordDebugStepInfo {
273    /// The debug trace node index when the recording starts.
274    pub start_node_idx: usize,
275    /// The original tracer config when the recording starts.
276    pub original_tracer_config: TracingInspectorConfig,
277}
278
279/// Environment overrides applied at the opcode level.
280///
281/// In isolation mode (and inside the synthetic transactions used by
282/// `--gas-report` / `--isolate`) the transaction environment is zeroed for
283/// fee-accounting purposes, so cheatcodes that mutate the env (e.g.
284/// `vm.fee`, `vm.txGasPrice`, `vm.blobhashes`) cannot rely on those
285/// mutations being visible to contracts via the `BASEFEE`, `GASPRICE` and
286/// `BLOBHASH` opcodes. These overrides are applied in `step_end` to fix
287/// the value that was just pushed onto the stack.
288///
289/// # Semantics when invoked from inside the synthetic isolation transaction
290///
291/// `vm.fee` / `vm.txGasPrice` / `vm.blobhashes` consult
292/// [`Cheatcodes::in_isolation_context`]; when set, they only update these
293/// overrides (so `tx.gas_price = 0` continues to apply to fee accounting and
294/// EIP-4844 inner-tx validation does not reject the synthetic call) and
295/// leave the real env untouched. After the inner transaction returns, the
296/// outer env is restored from the cached snapshot taken before
297/// `transact_inner`, which means:
298///
299/// - the override **does** persist for subsequent `BASEFEE`, `GASPRICE` and `BLOBHASH` reads (this
300///   hook fires in `step_end` regardless of isolation),
301/// - `vm.getBlobhashes()` also consults these overrides, so it returns the correct value.
302/// - but the real `block.basefee` / `tx.gas_price` / `tx.blob_hashes` do **not** reflect the
303///   cheatcode value, so other non-opcode env consumers will not see it.
304///
305/// Calling these cheatcodes outside isolation behaves as before (real env
306/// is also mutated and the override mirrors it).
307#[derive(Clone, Debug, Default)]
308pub struct EnvOverrides {
309    /// Override for the `BASEFEE` opcode (set via `vm.fee`).
310    pub basefee: Option<u64>,
311    /// Override for the `GASPRICE` opcode (set via `vm.txGasPrice`).
312    pub gas_price: Option<u128>,
313    /// Override for the `BLOBHASH` opcode (set via `vm.blobhashes`).
314    pub blob_hashes: Option<Vec<B256>>,
315    /// `tx.gas_price` captured at snapshot time when no gas_price override was
316    /// active. `sync_tx_after_env_override_restore` uses this to restore the
317    /// real pre-override value (not hardcoded 0) on revert.
318    pub pre_override_gas_price: Option<u128>,
319    /// `tx.tx_type` captured at snapshot time when no blob_hashes override was
320    /// active. Prevents tx_type being stuck at EIP4844 after reverting from a
321    /// blobhashes-set state.
322    pub pre_override_tx_type: Option<u8>,
323    /// `tx.blob_hashes` captured at snapshot time when no blob_hashes override
324    /// was active.
325    pub pre_override_blob_hashes: Option<Vec<B256>>,
326    /// The opcode about to run (captured in `step`, consumed in `step_end`),
327    /// used to know what was just executed when `step_end` fires — at that
328    /// point `interpreter.bytecode.opcode()` already points at the *next*
329    /// instruction.
330    pending_opcode: Option<u8>,
331    /// Pending index for the `BLOBHASH` opcode, captured in `step` (where
332    /// the index is still on top of the stack) for use in `step_end` (after
333    /// the opcode has consumed it and pushed the looked-up hash).
334    pending_blobhash_index: Option<u64>,
335}
336
337impl EnvOverrides {
338    /// Whether any override is set.
339    #[inline]
340    pub const fn is_any_set(&self) -> bool {
341        self.basefee.is_some() || self.gas_price.is_some() || self.blob_hashes.is_some()
342    }
343}
344
345/// A callback registered for a storage access hook.
346#[derive(Clone, Copy, Debug, PartialEq, Eq)]
347pub struct StorageHook {
348    /// Contract that receives the callback.
349    pub callback_target: Address,
350    /// Callback function selector.
351    pub callback_selector: [u8; 4],
352}
353
354#[derive(Clone, Debug)]
355enum PendingStorageHook {
356    Load {
357        account: Address,
358        slot: U256,
359        hook: StorageHook,
360    },
361    Store {
362        account: Address,
363        slot: U256,
364        old_value: U256,
365        mapping: Option<(B256, Vec<B256>)>,
366        hook: StorageHook,
367    },
368}
369
370#[derive(Clone, Debug)]
371struct ActiveStorageHook {
372    parent_depth: usize,
373    callback_target: Address,
374    callback_input: Bytes,
375    saved_gas: Gas,
376    saved_return_data: Bytes,
377    saved_stack_item: Option<U256>,
378    journal_start: usize,
379    inspector_state: StorageHookInspectorState,
380    outcome: Option<(InstructionResult, Bytes)>,
381}
382
383#[derive(Clone, Debug)]
384struct StorageHookInspectorState {
385    accesses: RecordAccess,
386    recording_accesses: bool,
387    mapping_slots: Option<AddressHashMap<MappingSlots>>,
388    recorded_logs: Option<Vec<Vm::Log>>,
389    mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
390    mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
391    expected_revert: Option<ExpectedRevert>,
392    assume_no_revert: Option<AssumeNoRevert>,
393    expected_calls: ExpectedCallTracker,
394    expected_emits: ExpectedEmitTracker,
395    expected_creates: Vec<ExpectedCreate>,
396}
397
398/// Holds gas metering state.
399#[derive(Clone, Debug, Default)]
400pub struct GasMetering {
401    /// True if gas metering is paused.
402    pub paused: bool,
403    /// True if gas metering was resumed or reset during the test.
404    /// Used to reconcile gas when frame ends (if spent less than refunded).
405    pub touched: bool,
406    /// True if gas metering should be reset to frame limit.
407    pub reset: bool,
408    /// Stores paused gas frames.
409    pub paused_frames: Vec<Gas>,
410
411    /// The group and name of the active snapshot.
412    pub active_gas_snapshot: Option<(String, String)>,
413
414    /// Cache of the amount of gas used in previous call.
415    /// This is used by the `lastCallGas` cheatcode.
416    pub last_call_gas: Option<crate::Vm::Gas>,
417    /// Gas used by `snapshotGasLastCall`.
418    pub(crate) last_call_snapshot_gas_used: u64,
419
420    /// Cache of the amount of gas used in previous call or create frame.
421    /// This is used by the `lastFrameGas` cheatcode.
422    pub last_frame_gas: Option<crate::Vm::Gas>,
423    /// Gas used by `snapshotGasLastFrame`.
424    pub(crate) last_frame_snapshot_gas_used: u64,
425
426    /// Post-refund gas used by the isolated transaction wrapping the current frame.
427    isolated_snapshot_gas_used: Option<u64>,
428
429    /// Isolated transaction refund to exclude from the next region sample at the caller's depth.
430    pending_isolated_refund: Option<(usize, u64)>,
431
432    /// True if gas recording is enabled.
433    pub recording: bool,
434    /// The gas used in the last frame.
435    pub last_gas_used: u64,
436    /// Gas records for the active snapshots.
437    pub gas_records: Vec<GasRecord>,
438}
439
440impl GasMetering {
441    /// Start the gas recording.
442    pub const fn start(&mut self) {
443        self.recording = true;
444        self.pending_isolated_refund = None;
445    }
446
447    /// Stop the gas recording.
448    pub const fn stop(&mut self) {
449        self.recording = false;
450    }
451
452    /// Resume paused gas metering.
453    pub fn resume(&mut self) {
454        if self.paused {
455            self.paused = false;
456            self.touched = true;
457        }
458        self.paused_frames.clear();
459    }
460
461    /// Reset gas to limit.
462    pub fn reset(&mut self) {
463        self.paused = false;
464        self.touched = true;
465        self.reset = true;
466        self.paused_frames.clear();
467    }
468
469    /// Preserves the historical gas snapshot value for an isolated transaction.
470    pub const fn set_isolated_snapshot_gas_used(&mut self, gas_used: u64) {
471        self.isolated_snapshot_gas_used = Some(gas_used);
472    }
473
474    /// Preserve post-refund region snapshots without changing the interpreter's gross gas usage.
475    const fn record_isolated_refund(
476        &mut self,
477        depth: usize,
478        gas: &Gas,
479        snapshot_gas_used: Option<u64>,
480    ) {
481        if self.recording
482            && let Some(snapshot_gas_used) = snapshot_gas_used
483        {
484            self.pending_isolated_refund =
485                Some((depth, gas.total_gas_spent().saturating_sub(snapshot_gas_used)));
486        }
487    }
488}
489
490/// Holds data about arbitrary storage.
491#[derive(Clone, Debug, Default)]
492pub struct ArbitraryStorage {
493    /// Mapping of arbitrary storage addresses to generated values (slot, arbitrary value).
494    /// (SLOADs return random value if storage slot wasn't accessed).
495    /// Changed values are recorded and used to copy storage to different addresses.
496    values: HashMap<Address, HashMap<U256, U256>>,
497    /// Mapping of address with storage copied to arbitrary storage address source.
498    copies: HashMap<Address, Address>,
499    /// Address with storage slots that should be overwritten even if previously set.
500    overwrites: HashSet<Address>,
501    /// Storage slots explicitly written with `vm.store`, grouped by address.
502    explicit_slots: HashMap<Address, HashSet<U256>>,
503}
504
505impl ArbitraryStorage {
506    /// Marks an address with arbitrary storage.
507    pub fn mark_arbitrary(&mut self, address: &Address, overwrite: bool) {
508        self.values.insert(*address, HashMap::default());
509        self.explicit_slots.remove(address);
510        if overwrite {
511            self.overwrites.insert(*address);
512        } else {
513            self.overwrites.remove(address);
514        }
515    }
516
517    /// Maps an address that copies storage with the arbitrary storage address.
518    pub fn mark_copy(&mut self, from: &Address, to: &Address) {
519        if self.values.contains_key(from) {
520            self.copies.insert(*to, *from);
521            if let Some(slots) = self.explicit_slots.get(from).cloned() {
522                self.explicit_slots.insert(*to, slots);
523            } else {
524                self.explicit_slots.remove(to);
525            }
526        }
527    }
528
529    /// Marks a slot as explicitly written if the address has arbitrary or copied storage.
530    fn mark_explicit(&mut self, address: Address, slot: U256) {
531        if self.values.contains_key(&address) || self.copies.contains_key(&address) {
532            self.explicit_slots.entry(address).or_default().insert(slot);
533        }
534    }
535
536    /// Returns whether a slot was explicitly written for the given address.
537    fn is_explicit(&self, address: Address, slot: U256) -> bool {
538        self.explicit_slots.get(&address).is_some_and(|slots| slots.contains(&slot))
539    }
540
541    /// Returns addresses explicitly marked with arbitrary storage.
542    fn targets(&self) -> impl Iterator<Item = Address> + '_ {
543        self.values.keys().copied()
544    }
545
546    /// Returns addresses explicitly marked with arbitrary storage and whether nonzero slots are
547    /// overwritten.
548    fn target_overwrite_modes(&self) -> impl Iterator<Item = (Address, bool)> + '_ {
549        self.values.keys().map(|address| (*address, self.overwrites.contains(address)))
550    }
551
552    /// Returns addresses that copy storage from arbitrary-storage targets.
553    fn copied_targets(&self) -> impl Iterator<Item = Address> + '_ {
554        self.copies.keys().copied()
555    }
556
557    /// Returns copied arbitrary-storage targets and their source address.
558    fn copied_target_sources(&self) -> impl Iterator<Item = (Address, Address)> + '_ {
559        self.copies.iter().map(|(target, source)| (*target, *source))
560    }
561
562    /// Caches a concrete value for a slot on an arbitrary-storage address or copied target.
563    fn cache_value(&mut self, address: Address, slot: U256, data: U256) {
564        if let Some(values) = self.values.get_mut(&address) {
565            values.insert(slot, data);
566            return;
567        }
568
569        let Some(source) = self.copies.get(&address).copied() else {
570            return;
571        };
572        if let Some(values) = self.values.get_mut(&source) {
573            values.insert(slot, data);
574        }
575    }
576
577    /// Returns a cached arbitrary value for a slot.
578    fn cached_value(&self, address: Address, slot: U256) -> Option<U256> {
579        self.values.get(&address).and_then(|values| values.get(&slot)).copied()
580    }
581
582    /// Saves arbitrary storage value for a given address:
583    /// - store value in changed values cache.
584    /// - update account's storage with given value.
585    pub fn save<CTX: ContextTr>(
586        &mut self,
587        ecx: &mut CTX,
588        address: Address,
589        slot: U256,
590        data: U256,
591    ) {
592        self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data);
593        if ecx.journal_mut().load_account(address).is_ok() {
594            ecx.journal_mut()
595                .sstore(address, slot, data)
596                .expect("could not set arbitrary storage value");
597        }
598    }
599
600    /// Copies arbitrary storage value from source address to the given target address:
601    /// - if a value is present in arbitrary values cache, then update target storage and return
602    ///   existing value.
603    /// - if no value was yet generated for given slot, then save new value in cache and update both
604    ///   source and target storages.
605    pub fn copy<CTX: ContextTr>(
606        &mut self,
607        ecx: &mut CTX,
608        target: Address,
609        slot: U256,
610        new_value: U256,
611    ) -> U256 {
612        let source = self.copies.get(&target).expect("missing arbitrary copy target entry");
613        let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage");
614        let value = match storage_cache.get(&slot) {
615            Some(value) => *value,
616            None => {
617                storage_cache.insert(slot, new_value);
618                // Update source storage with new value.
619                if ecx.journal_mut().load_account(*source).is_ok() {
620                    ecx.journal_mut()
621                        .sstore(*source, slot, new_value)
622                        .expect("could not copy arbitrary storage value");
623                }
624                new_value
625            }
626        };
627        // Update target storage with new value.
628        if ecx.journal_mut().load_account(target).is_ok() {
629            ecx.journal_mut().sstore(target, slot, value).expect("could not set storage");
630        }
631        value
632    }
633}
634
635/// List of transactions that can be broadcasted.
636pub type BroadcastableTransactions<N> = VecDeque<BroadcastableTransaction<N>>;
637
638#[derive(Clone, Copy, Debug, PartialEq, Eq)]
639enum CreatedAccountsFrameKind {
640    Call,
641    Create,
642}
643
644#[derive(Clone, Copy, Debug)]
645struct CreatedAccountsFrame {
646    kind: CreatedAccountsFrameKind,
647    depth: usize,
648    checkpoint: usize,
649}
650
651#[derive(Clone, Copy, Debug)]
652struct CreatedAccountChange {
653    fork_id: Option<LocalForkId>,
654    address: Address,
655    creation: usize,
656    previous: Option<usize>,
657    committed: bool,
658}
659
660#[derive(Clone, Debug)]
661struct CreatedAccountsSnapshot {
662    fork_id: Option<LocalForkId>,
663    bindings: AddressHashMap<usize>,
664}
665
666/// An EVM inspector that handles calls to various cheatcodes, each with their own behavior.
667///
668/// Cheatcodes can be called by contracts during execution to modify the VM environment, such as
669/// mocking addresses, signatures and altering call reverts.
670///
671/// Executing cheatcodes can be very powerful. Most cheatcodes are limited to evm internals, but
672/// there are also cheatcodes like `ffi` which can execute arbitrary commands or `writeFile` and
673/// `readFile` which can manipulate files of the filesystem. Therefore, several restrictions are
674/// implemented for these cheatcodes:
675/// - `ffi`, and file cheatcodes are _always_ opt-in (via foundry config) and never enabled by
676///   default: all respective cheatcode handlers implement the appropriate checks
677/// - File cheatcodes require explicit permissions which paths are allowed for which operation, see
678///   `Config.fs_permission`
679/// - Only permitted accounts are allowed to execute cheatcodes in forking mode, this ensures no
680///   contract deployed on the live network is able to execute cheatcodes by simply calling the
681///   cheatcode address: by default, the caller, test contract and newly deployed contracts are
682///   allowed to execute cheatcodes
683#[derive(Clone, Debug)]
684pub struct Cheatcodes<FEN: FoundryEvmNetwork = EthEvmNetwork> {
685    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities
686    pub analysis: Option<CheatcodeAnalysis>,
687
688    /// The block environment
689    ///
690    /// Used in the cheatcode handler to overwrite the block environment separately from the
691    /// execution block environment.
692    pub block: Option<BlockEnvFor<FEN>>,
693
694    /// The active fork block override updated by a fork-switching cheatcode.
695    ///
696    /// This persists fork changes made through a copy-on-write backend between invariant calls.
697    pub fork_block_number_override: Option<u64>,
698
699    /// Currently active EIP-7702 delegations that will be consumed when building the next
700    /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during
701    /// transaction construction.
702    pub active_delegations: Vec<SignedAuthorization>,
703
704    /// The active EIP-4844 blob that will be attached to the next call.
705    pub active_blob_sidecar: Option<BlobTransactionSidecarVariant>,
706
707    /// The gas price.
708    ///
709    /// Used in the cheatcode handler to overwrite the gas price separately from the gas price
710    /// in the execution environment.
711    pub gas_price: Option<u128>,
712
713    /// Address labels
714    pub labels: AddressHashMap<String>,
715
716    /// Prank information, mapped to the call depth where pranks were added.
717    pub pranks: BTreeMap<usize, Prank>,
718
719    /// Expected revert information
720    pub expected_revert: Option<ExpectedRevert>,
721
722    /// Assume next call can revert and discard fuzz run if it does.
723    pub assume_no_revert: Option<AssumeNoRevert>,
724
725    /// Additional diagnostic for reverts
726    pub fork_revert_diagnostic: Option<RevertDiagnostic>,
727
728    /// Recorded storage reads and writes
729    pub accesses: RecordAccess,
730
731    /// Whether storage access recording is currently active
732    pub recording_accesses: bool,
733
734    /// Recorded account accesses (calls, creates) organized by relative call depth, where the
735    /// topmost vector corresponds to accesses at the depth at which account access recording
736    /// began. Each vector in the matrix represents a list of accesses at a specific call
737    /// depth. Once that call context has ended, the last vector is removed from the matrix and
738    /// merged into the previous vector.
739    pub recorded_account_diffs_stack: Option<Vec<Vec<AccountAccess>>>,
740
741    /// Account accesses performed by the test runner before user code can start recording.
742    pending_account_diffs: Option<Arc<[AccountAccess]>>,
743
744    /// Completed account accesses prepended to the active user recording session.
745    recorded_account_diffs_prefix: Option<Arc<[AccountAccess]>>,
746
747    /// Successfully created accounts in execution order.
748    created_accounts: Vec<Address>,
749
750    /// The creation currently represented by each address on each fork.
751    created_account_bindings: HashMap<(Option<LocalForkId>, Address), usize>,
752
753    /// Revertible changes to creation bindings made by EVM create frames.
754    created_account_changes: Vec<CreatedAccountChange>,
755
756    /// Creation-list checkpoints for frames observed by this inspector.
757    created_accounts_frames: Vec<CreatedAccountsFrame>,
758
759    /// Creation lists captured by state snapshots.
760    created_accounts_snapshots: HashMap<U256, CreatedAccountsSnapshot>,
761
762    /// The information of the debug step recording.
763    pub record_debug_steps_info: Option<RecordDebugStepInfo>,
764
765    /// Recorded logs
766    pub recorded_logs: Option<Vec<crate::Vm::Log>>,
767
768    /// Mocked calls
769    // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext`
770    pub mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
771
772    /// Mocked functions. Maps target address to be mocked to pair of (calldata, mock address).
773    pub mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
774
775    /// Expected calls
776    pub expected_calls: ExpectedCallTracker,
777    /// Expected emits
778    pub expected_emits: ExpectedEmitTracker,
779    /// Expected creates
780    pub expected_creates: Vec<ExpectedCreate>,
781
782    /// Map of context depths to memory offset ranges that may be written to within the call depth.
783    pub allowed_mem_writes: HashMap<u64, Vec<Range<u64>>>,
784
785    /// Current broadcasting information
786    pub broadcast: Option<Broadcast>,
787
788    /// Scripting based transactions
789    pub broadcastable_transactions: BroadcastableTransactions<FEN::Network>,
790
791    /// Current EIP-2930 access lists.
792    pub access_list: Option<AccessList>,
793
794    /// Additional, user configurable context this Inspector has access to when inspecting a call.
795    pub config: Arc<CheatsConfig>,
796
797    /// Additional addresses recognized as cheatcode contracts by this executor.
798    pub extra_cheatcode_addresses: &'static [Address],
799
800    /// Test-scoped context holding data that needs to be reset every test run
801    pub test_context: TestContext,
802
803    /// Revert payloads minted by the `skip` cheatcode during the current test call.
804    ///
805    /// A top-level revert is only classified as a skip when its data byte-equals one of these
806    /// payloads, so user-crafted `FOUNDRY::SKIP` revert data never skips a test on its own.
807    pub skip_payloads: Vec<Bytes>,
808
809    /// Whether to commit FS changes such as file creations, writes and deletes.
810    /// Used to prevent duplicate changes file executing non-committing calls.
811    pub fs_commit: bool,
812
813    /// Serialized JSON values.
814    // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic.
815    pub serialized_jsons: BTreeMap<String, BTreeMap<String, Value>>,
816
817    /// All recorded ETH `deal`s.
818    pub eth_deals: Vec<DealRecord>,
819
820    /// Gas metering state.
821    pub gas_metering: GasMetering,
822
823    /// Contains gas snapshots made over the course of a test suite.
824    // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic.
825    pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
826
827    /// Mapping slots.
828    pub mapping_slots: Option<AddressHashMap<MappingSlots>>,
829
830    /// The current program counter.
831    pub pc: usize,
832    /// Breakpoints supplied by the `breakpoint` cheatcode.
833    /// `char -> (address, pc)`
834    pub breakpoints: Breakpoints,
835
836    /// Whether the next contract creation should be intercepted to return its initcode.
837    pub intercept_next_create_call: bool,
838
839    /// Optional cheatcodes `TestRunner`. Used for generating random values from uint and int
840    /// strategies.
841    test_runner: Option<TestRunner>,
842
843    /// Ignored traces.
844    pub ignored_traces: IgnoredTraces,
845
846    /// Addresses with arbitrary storage.
847    pub arbitrary_storage: Option<ArbitraryStorage>,
848
849    /// SLOAD callbacks keyed by effective storage address.
850    storage_load_hooks: AddressHashMap<StorageHook>,
851    /// SSTORE callbacks keyed by effective storage address.
852    storage_store_hooks: AddressHashMap<StorageHook>,
853    /// Mapping SSTORE callbacks keyed by effective storage address and root slot.
854    mapping_storage_store_hooks: AddressHashMap<HashMap<B256, StorageHook>>,
855    /// Execution-local provenance used only by mapping storage hooks.
856    storage_hook_mapping_slots: AddressHashMap<MappingSlots>,
857    /// A 64-byte Keccak operation awaiting successful completion.
858    pending_mapping_hash: Option<PendingMappingHash>,
859    /// Whether any storage hook map contains a callback.
860    storage_hooks_registered: bool,
861    /// Matching storage access captured before the opcode executes.
862    pending_storage_hook: Option<PendingStorageHook>,
863    /// Synthetic callback frame currently executing or awaiting parent cleanup.
864    active_storage_hook: Option<ActiveStorageHook>,
865
866    /// Deprecated cheatcodes mapped to the reason. Used to report warnings on test results.
867    pub deprecated: HashMap<&'static str, Option<&'static str>>,
868    /// Main script contract, when script execution protection is enabled.
869    pub script_address: Option<Address>,
870    /// Unlocked wallets used in scripts and testing of scripts.
871    pub wallets: Option<Wallets>,
872    /// Parsed secp256k1 private-key signers for repeated `vm.addr` / `vm.sign` calls.
873    pub private_key_signers: HashMap<U256, PrivateKeySigner>,
874    /// Signatures identifier for decoding events and functions
875    signatures_identifier: OnceLock<Option<SignaturesIdentifier>>,
876    /// Used to determine whether the broadcasted call has dynamic gas limit.
877    pub dynamic_gas_limit: bool,
878    // Custom execution evm version.
879    pub execution_evm_version: Option<SpecFor<FEN>>,
880
881    /// Opcode-level environment overrides for `BASEFEE`, `GASPRICE` and
882    /// `BLOBHASH`. Set by `vm.fee`, `vm.txGasPrice`, `vm.blobhashes` and
883    /// applied in [`Inspector::step_end`].
884    ///
885    /// Needed because in isolation mode the synthetic inner transaction
886    /// zeroes the corresponding tx/block env fields for fee-accounting.
887    ///
888    /// Keyed by active fork ID (`None` -> local) so that multi-fork tests do not bleed overrides
889    /// across forks when `vm.selectFork` / `vm.createSelectFork` switches the active fork.
890    pub env_overrides: HashMap<Option<LocalForkId>, EnvOverrides>,
891
892    /// Per-state-snapshot copies of [`Self::env_overrides`], captured by
893    /// `vm.snapshotState` and restored by `vm.revertToState[AndDelete]`.
894    ///
895    /// `env_overrides` lives on the cheatcode inspector rather than in
896    /// `EvmEnv`, so the backend's snapshot/revert mechanism does not see
897    /// it. Without this, an override set after a snapshot would survive a
898    /// `revertToState`, and the BASEFEE/GASPRICE/BLOBHASH opcodes (which
899    /// the override layer rewrites in `step_end`) would keep returning
900    /// the post-snapshot value even though `EvmEnv` was rolled back.
901    pub env_overrides_snapshots: HashMap<U256, HashMap<Option<LocalForkId>, EnvOverrides>>,
902
903    /// Per-state-snapshot copies of [`Self::fork_block_number_override`].
904    pub fork_block_number_override_snapshots: HashMap<U256, Option<u64>>,
905
906    /// Transaction-position context and Monad's reserve-balance-tracker state captured atomically
907    /// alongside state snapshots.
908    #[cfg(feature = "monad")]
909    pub context_snapshots:
910        HashMap<U256, (ChainFor<FEN>, monad_revm::reserve_balance::tracker::ReserveBalanceTracker)>,
911
912    /// Whether we are currently executing inside an isolation context, i.e.
913    /// the synthetic inner transaction wrapped by
914    /// `InspectorStackRefMut::transact_inner` (used by `--gas-report` and
915    /// `--isolate`).
916    ///
917    /// Toggled by the inspector stack around the inner `transact_raw`
918    /// call. Cheatcodes that mutate the tx/block env consult this flag and
919    /// route the change through `EnvOverrides` instead of the actual env
920    /// when `true`, so they don't fight with the fee-accounting zeroing.
921    pub in_isolation_context: bool,
922}
923
924// This is not derived because calling this in `fn new` with `..Default::default()` creates a second
925// `CheatsConfig` which is unused, and inside it `ProjectPathsConfig` is relatively expensive to
926// create.
927impl Default for Cheatcodes {
928    fn default() -> Self {
929        Self::new(Arc::default())
930    }
931}
932
933impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
934    /// Creates a new `Cheatcodes` with the given settings.
935    pub fn new(config: Arc<CheatsConfig>) -> Self {
936        Self {
937            analysis: None,
938            fs_commit: true,
939            labels: config.labels.clone(),
940            config,
941            extra_cheatcode_addresses: &[],
942            block: Default::default(),
943            fork_block_number_override: Default::default(),
944            active_delegations: Default::default(),
945            active_blob_sidecar: Default::default(),
946            gas_price: Default::default(),
947            pranks: Default::default(),
948            expected_revert: Default::default(),
949            assume_no_revert: Default::default(),
950            fork_revert_diagnostic: Default::default(),
951            accesses: Default::default(),
952            recording_accesses: Default::default(),
953            recorded_account_diffs_stack: Default::default(),
954            pending_account_diffs: Default::default(),
955            recorded_account_diffs_prefix: Default::default(),
956            created_accounts: Default::default(),
957            created_account_bindings: Default::default(),
958            created_account_changes: Default::default(),
959            created_accounts_frames: Default::default(),
960            created_accounts_snapshots: Default::default(),
961            recorded_logs: Default::default(),
962            record_debug_steps_info: Default::default(),
963            mocked_calls: Default::default(),
964            mocked_functions: Default::default(),
965            expected_calls: Default::default(),
966            expected_emits: Default::default(),
967            expected_creates: Default::default(),
968            allowed_mem_writes: Default::default(),
969            broadcast: Default::default(),
970            broadcastable_transactions: Default::default(),
971            access_list: Default::default(),
972            test_context: Default::default(),
973            skip_payloads: Default::default(),
974            serialized_jsons: Default::default(),
975            eth_deals: Default::default(),
976            gas_metering: Default::default(),
977            gas_snapshots: Default::default(),
978            mapping_slots: Default::default(),
979            pc: Default::default(),
980            breakpoints: Default::default(),
981            intercept_next_create_call: Default::default(),
982            test_runner: Default::default(),
983            ignored_traces: Default::default(),
984            arbitrary_storage: Default::default(),
985            storage_load_hooks: Default::default(),
986            storage_store_hooks: Default::default(),
987            mapping_storage_store_hooks: Default::default(),
988            storage_hook_mapping_slots: Default::default(),
989            pending_mapping_hash: Default::default(),
990            storage_hooks_registered: Default::default(),
991            pending_storage_hook: Default::default(),
992            active_storage_hook: Default::default(),
993            deprecated: Default::default(),
994            script_address: Default::default(),
995            wallets: Default::default(),
996            private_key_signers: Default::default(),
997            signatures_identifier: Default::default(),
998            dynamic_gas_limit: Default::default(),
999            execution_evm_version: None,
1000            env_overrides: Default::default(),
1001            env_overrides_snapshots: Default::default(),
1002            fork_block_number_override_snapshots: Default::default(),
1003            #[cfg(feature = "monad")]
1004            context_snapshots: Default::default(),
1005            in_isolation_context: false,
1006        }
1007    }
1008
1009    /// Sets additional addresses recognized as cheatcode contracts.
1010    #[inline]
1011    pub const fn set_extra_cheatcode_addresses(&mut self, addresses: &'static [Address]) {
1012        self.extra_cheatcode_addresses = addresses;
1013    }
1014
1015    /// Enables cheatcode analysis capabilities by providing a solar compiler instance.
1016    pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) {
1017        self.analysis = Some(analysis);
1018    }
1019
1020    /// Starts an internal account diff recording session for test runner setup.
1021    pub fn start_internal_state_diff_recording(&mut self) -> bool {
1022        if self.recorded_account_diffs_stack.is_some()
1023            || self.recorded_account_diffs_prefix.is_some()
1024        {
1025            return false;
1026        }
1027        self.recorded_account_diffs_stack = Some(Default::default());
1028        true
1029    }
1030
1031    /// Stops an internal account diff recording session without leaving recording enabled.
1032    pub fn stop_internal_state_diff_recording(&mut self) -> Vec<AccountAccess> {
1033        self.recorded_account_diffs_stack.take().unwrap_or_default().into_iter().flatten().collect()
1034    }
1035
1036    /// Makes account accesses captured by the test runner available to the next recording session.
1037    pub fn set_pending_account_diffs(&mut self, accesses: Vec<AccountAccess>) {
1038        self.pending_account_diffs = (!accesses.is_empty()).then(|| Arc::from(accesses));
1039    }
1040
1041    /// Starts a user account diff recording session, including pending test runner accesses.
1042    pub fn start_state_diff_recording(&mut self) {
1043        self.recorded_account_diffs_prefix = self.pending_account_diffs.take();
1044        self.recorded_account_diffs_stack = Some(Default::default());
1045    }
1046
1047    /// Returns completed and active account accesses in execution order.
1048    pub fn recorded_account_diffs(&self) -> impl Iterator<Item = &AccountAccess> {
1049        self.recorded_account_diffs_prefix
1050            .iter()
1051            .flat_map(|prefix| prefix.iter())
1052            .chain(self.recorded_account_diffs_stack.iter().flatten().flatten())
1053    }
1054
1055    /// Takes completed account accesses from the active user recording session.
1056    pub fn take_recorded_account_diffs_prefix(&mut self) -> Vec<AccountAccess> {
1057        self.recorded_account_diffs_prefix
1058            .take()
1059            .map(|prefix| prefix.as_ref().to_vec())
1060            .unwrap_or_default()
1061    }
1062
1063    /// Returns the current creation bound to each address on the given fork.
1064    pub(crate) fn created_account_bindings(
1065        &self,
1066        fork_id: Option<LocalForkId>,
1067    ) -> AddressHashMap<usize> {
1068        self.created_account_bindings
1069            .iter()
1070            .filter_map(|(&(event_fork_id, address), &creation)| {
1071                (event_fork_id == fork_id).then_some((address, creation))
1072            })
1073            .collect()
1074    }
1075
1076    /// Returns successfully created accounts bound to the given fork in creation order.
1077    pub(crate) fn created_accounts(&self, fork_id: Option<LocalForkId>) -> Vec<Address> {
1078        let bindings = self.created_account_bindings(fork_id);
1079        self.created_accounts
1080            .iter()
1081            .enumerate()
1082            .filter_map(|(index, &address)| {
1083                (bindings.get(&address) == Some(&index)).then_some(address)
1084            })
1085            .collect()
1086    }
1087
1088    /// Records a successfully created account.
1089    pub(crate) fn record_created_account(
1090        &mut self,
1091        fork_id: Option<LocalForkId>,
1092        address: Address,
1093    ) {
1094        let creation = self.created_accounts.len();
1095        self.created_accounts.push(address);
1096        let previous = self.created_account_bindings.insert((fork_id, address), creation);
1097        self.created_account_changes.push(CreatedAccountChange {
1098            fork_id,
1099            address,
1100            creation,
1101            previous,
1102            committed: false,
1103        });
1104    }
1105
1106    /// Keeps creation bindings saved with an outgoing fork across later frame reverts.
1107    pub(crate) fn commit_created_account_changes(&mut self, fork_id: Option<LocalForkId>) {
1108        for change in &mut self.created_account_changes {
1109            if change.fork_id == fork_id {
1110                change.committed = true;
1111            }
1112        }
1113    }
1114
1115    /// Records shared pre-fork creations on a newly selected fork without replacing local ones.
1116    pub(crate) fn record_initial_created_accounts(
1117        &mut self,
1118        fork_id: Option<LocalForkId>,
1119        accounts: impl IntoIterator<Item = (Address, usize)>,
1120    ) {
1121        for (address, creation) in accounts {
1122            self.created_account_bindings.entry((fork_id, address)).or_insert(creation);
1123        }
1124    }
1125
1126    /// Records creations propagated to a fork with persistent account state.
1127    pub(crate) fn record_propagated_accounts(
1128        &mut self,
1129        fork_id: Option<LocalForkId>,
1130        accounts: impl IntoIterator<Item = (Address, usize)>,
1131    ) {
1132        self.created_account_bindings
1133            .extend(accounts.into_iter().map(|(address, creation)| ((fork_id, address), creation)));
1134    }
1135
1136    /// Captures creation ordering alongside a state snapshot.
1137    pub(crate) fn snapshot_created_accounts(
1138        &mut self,
1139        snapshot_id: U256,
1140        fork_id: Option<LocalForkId>,
1141    ) {
1142        let bindings = self.created_account_bindings(fork_id);
1143        self.created_accounts_snapshots
1144            .insert(snapshot_id, CreatedAccountsSnapshot { fork_id, bindings });
1145    }
1146
1147    /// Restores creation ordering from a state snapshot.
1148    pub(crate) fn revert_created_accounts(&mut self, snapshot_id: U256, remove: bool) {
1149        let snapshot = if remove {
1150            self.created_accounts_snapshots.remove(&snapshot_id)
1151        } else {
1152            self.created_accounts_snapshots.get(&snapshot_id).cloned()
1153        };
1154        if let Some(snapshot) = snapshot {
1155            self.created_account_bindings.retain(|(fork_id, _), _| *fork_id != snapshot.fork_id);
1156            self.created_account_bindings.extend(
1157                snapshot
1158                    .bindings
1159                    .into_iter()
1160                    .map(|(address, creation)| ((snapshot.fork_id, address), creation)),
1161            );
1162        }
1163    }
1164
1165    /// Deletes one captured creation-order snapshot.
1166    pub(crate) fn delete_created_accounts_snapshot(&mut self, snapshot_id: U256) {
1167        self.created_accounts_snapshots.remove(&snapshot_id);
1168    }
1169
1170    /// Deletes all captured creation-order snapshots.
1171    pub(crate) fn clear_created_accounts_snapshots(&mut self) {
1172        self.created_accounts_snapshots.clear();
1173    }
1174
1175    fn start_created_accounts_frame(
1176        &mut self,
1177        reset: bool,
1178        kind: CreatedAccountsFrameKind,
1179        depth: usize,
1180    ) {
1181        if reset {
1182            self.created_accounts.clear();
1183            self.created_account_bindings.clear();
1184            self.created_account_changes.clear();
1185            self.created_accounts_frames.clear();
1186            // Earlier snapshots contain no creations from the new root transaction.
1187            for snapshot in self.created_accounts_snapshots.values_mut() {
1188                snapshot.bindings.clear();
1189            }
1190        }
1191        self.created_accounts_frames.push(CreatedAccountsFrame {
1192            kind,
1193            depth,
1194            checkpoint: self.created_account_changes.len(),
1195        });
1196    }
1197
1198    fn finish_created_accounts_frame(
1199        &mut self,
1200        success: bool,
1201        kind: CreatedAccountsFrameKind,
1202        depth: usize,
1203    ) {
1204        let Some(frame) = self
1205            .created_accounts_frames
1206            .last()
1207            .copied()
1208            .filter(|frame| frame.kind == kind && frame.depth == depth)
1209        else {
1210            return;
1211        };
1212        let checkpoint = frame.checkpoint;
1213        self.created_accounts_frames.pop();
1214        if !success {
1215            while self.created_account_changes.len() > checkpoint {
1216                let change = self.created_account_changes.pop().expect("length checked");
1217                if change.committed {
1218                    continue;
1219                }
1220                let key = (change.fork_id, change.address);
1221                if self.created_account_bindings.get(&key) != Some(&change.creation) {
1222                    continue;
1223                }
1224                if let Some(previous) = change.previous {
1225                    self.created_account_bindings.insert(key, previous);
1226                } else {
1227                    self.created_account_bindings.remove(&key);
1228                }
1229            }
1230        }
1231    }
1232
1233    /// Returns the env overrides for the given fork (`None` = no-fork / local).
1234    pub fn env_overrides_for(&self, fork_id: Option<U256>) -> Option<&EnvOverrides> {
1235        self.env_overrides.get(&fork_id).filter(|o| o.is_any_set())
1236    }
1237
1238    /// Returns a mutable reference to the env overrides for the given fork, inserting a
1239    /// default entry if absent.
1240    pub fn env_overrides_for_mut(&mut self, fork_id: Option<U256>) -> &mut EnvOverrides {
1241        self.env_overrides.entry(fork_id).or_default()
1242    }
1243
1244    /// Returns the configured prank at given depth or the first prank configured at a lower depth.
1245    /// For example, if pranks configured for depth 1, 3 and 5, the prank for depth 4 is the one
1246    /// configured at depth 3.
1247    pub fn get_prank(&self, depth: usize) -> Option<&Prank> {
1248        self.pranks.range(..=depth).last().map(|(_, prank)| prank)
1249    }
1250
1251    /// Returns the configured wallets if available, else creates a new instance.
1252    pub fn wallets(&mut self) -> &Wallets {
1253        self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None))
1254    }
1255
1256    /// Sets the unlocked wallets.
1257    pub fn set_wallets(&mut self, wallets: Wallets) {
1258        self.wallets = Some(wallets);
1259    }
1260
1261    /// Adds a delegation to the active delegations list.
1262    pub fn add_delegation(&mut self, authorization: SignedAuthorization) {
1263        self.active_delegations.push(authorization);
1264    }
1265
1266    /// Returns the signatures identifier.
1267    pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> {
1268        self.signatures_identifier
1269            .get_or_init(|| {
1270                if let Some(artifacts) = &self.config.available_artifacts {
1271                    return SignaturesIdentifier::new_offline_with_abis(
1272                        artifacts.values().map(|contract| &contract.abi),
1273                    )
1274                    .ok();
1275                }
1276                SignaturesIdentifier::new(true).ok()
1277            })
1278            .as_ref()
1279    }
1280
1281    /// Decodes the input data and applies the cheatcode.
1282    fn apply_cheatcode(
1283        &mut self,
1284        ecx: &mut FoundryContextFor<'_, FEN>,
1285        call: &CallInputs,
1286        executor: &mut dyn CheatcodesExecutor<FEN>,
1287    ) -> Result {
1288        // decode the cheatcode call
1289        let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| {
1290            if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e {
1291                let msg = format!(
1292                    "unknown cheatcode with selector {selector}; \
1293                     you may have a mismatch between the `Vm` interface (likely in `forge-std`) \
1294                     and the `forge` version"
1295                );
1296                return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg));
1297            }
1298            e
1299        })?;
1300
1301        let caller = call.caller;
1302
1303        // ensure the caller is allowed to execute cheatcodes,
1304        // but only if the backend is in forking mode
1305        ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
1306
1307        apply_dispatch(
1308            &decoded,
1309            &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
1310            executor,
1311        )
1312    }
1313
1314    /// Decodes the input data and applies Monad-specific cheatcodes.
1315    #[cfg(feature = "monad")]
1316    fn apply_monad_cheatcode(
1317        &mut self,
1318        ecx: &mut FoundryContextFor<'_, FEN>,
1319        call: &CallInputs,
1320    ) -> Result {
1321        let input = call.input.bytes(ecx);
1322        let caller = call.caller;
1323
1324        // ensure the caller is allowed to execute cheatcodes,
1325        // but only if the backend is in forking mode
1326        ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
1327
1328        crate::monad::apply_monad_cheatcode(
1329            &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
1330            &input,
1331        )
1332    }
1333
1334    /// Grants cheat code access for new contracts if the caller also has
1335    /// cheatcode access or the new contract is created in top most call.
1336    ///
1337    /// There may be cheatcodes in the constructor of the new contract, in order to allow them
1338    /// automatically we need to determine the new address.
1339    fn allow_cheatcodes_on_create(
1340        &self,
1341        ecx: &mut FoundryContextFor<FEN>,
1342        caller: Address,
1343        created_address: Address,
1344    ) {
1345        if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) {
1346            ecx.db_mut().allow_cheatcode_access(created_address);
1347        }
1348    }
1349
1350    /// Apply EIP-2930 access list.
1351    ///
1352    /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to
1353    /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support
1354    /// access lists themselves.
1355    fn apply_accesslist(&mut self, ecx: &mut FoundryContextFor<FEN>) {
1356        if let Some(access_list) = &self.access_list {
1357            ecx.tx_mut().set_access_list(access_list.clone());
1358
1359            if ecx.tx().tx_type() == TransactionType::Legacy as u8 {
1360                ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8);
1361            }
1362        }
1363    }
1364
1365    /// Called when there was a revert.
1366    ///
1367    /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's
1368    /// revert would run into issues.
1369    pub fn on_revert(&mut self, ecx: &mut FoundryContextFor<FEN>) {
1370        trace!(deals=?self.eth_deals.len(), "rolling back deals");
1371
1372        // Delay revert clean up until expected revert is handled, if set.
1373        if self.expected_revert.is_some() {
1374            return;
1375        }
1376
1377        // we only want to apply cleanup top level
1378        if ecx.journal().depth() > 0 {
1379            return;
1380        }
1381
1382        // Roll back all previously applied deals
1383        // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine
1384        // which rolls back any transfers.
1385        while let Some(record) = self.eth_deals.pop() {
1386            if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) {
1387                acc.info.balance = record.old_balance;
1388            }
1389        }
1390    }
1391
1392    /// Handles a call, accounting for whether the executor will isolate it as a transaction.
1393    ///
1394    /// If `isolate_call` is true, the executor owns the transaction nonce increment when the call
1395    /// proceeds to execution.
1396    pub fn call_with_executor(
1397        &mut self,
1398        ecx: &mut FoundryContextFor<'_, FEN>,
1399        call: &mut CallInputs,
1400        executor: &mut dyn CheatcodesExecutor<FEN>,
1401        isolate_call: bool,
1402    ) -> Option<CallOutcome> {
1403        // Apply custom execution evm version.
1404        if let Some(spec_id) = self.execution_evm_version {
1405            ecx.set_spec_and_gas_params(spec_id);
1406        }
1407
1408        let gas = Gas::new(call.gas_limit);
1409        let curr_depth = ecx.journal().depth();
1410        self.start_created_accounts_frame(
1411            curr_depth == 0,
1412            CreatedAccountsFrameKind::Call,
1413            curr_depth,
1414        );
1415
1416        // At the root call to test function or script `run()`/`setUp()` functions, we are
1417        // decreasing sender nonce to ensure that it matches on-chain nonce once we start
1418        // broadcasting.
1419        if curr_depth == 0 {
1420            let sender = ecx.tx().caller();
1421            let account = match super::evm::journaled_account(ecx, sender) {
1422                Ok(account) => account,
1423                Err(err) => {
1424                    return Some(CallOutcome {
1425                        result: InterpreterResult {
1426                            result: InstructionResult::Revert,
1427                            output: err.abi_encode().into(),
1428                            gas,
1429                        },
1430                        memory_offset: call.return_memory_offset.clone(),
1431                        was_precompile_called: false,
1432                        precompile_call_logs: vec![],
1433                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1434                    });
1435                }
1436            };
1437            let prev = account.info.nonce;
1438            account.info.nonce = prev.saturating_sub(1);
1439
1440            trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
1441        }
1442
1443        if call.target_address == CHEATCODE_ADDRESS {
1444            return match self.apply_cheatcode(ecx, call, executor) {
1445                Ok(retdata) => Some(CallOutcome {
1446                    result: InterpreterResult {
1447                        result: InstructionResult::Return,
1448                        output: retdata.into(),
1449                        gas,
1450                    },
1451                    memory_offset: call.return_memory_offset.clone(),
1452                    was_precompile_called: true,
1453                    precompile_call_logs: vec![],
1454                    charged_new_account_state_gas: call.charged_new_account_state_gas,
1455                }),
1456                Err(err) => Some(CallOutcome {
1457                    result: InterpreterResult {
1458                        result: InstructionResult::Revert,
1459                        output: err.abi_encode().into(),
1460                        gas,
1461                    },
1462                    memory_offset: call.return_memory_offset.clone(),
1463                    was_precompile_called: false,
1464                    precompile_call_logs: vec![],
1465                    charged_new_account_state_gas: call.charged_new_account_state_gas,
1466                }),
1467            };
1468        }
1469
1470        #[cfg(feature = "monad")]
1471        if crate::monad::is_monad_cheatcode_call(
1472            self.extra_cheatcode_addresses,
1473            call.target_address,
1474        ) {
1475            let checkpoint = ecx.journal_mut().checkpoint();
1476            return match self.apply_monad_cheatcode(ecx, call) {
1477                Ok(retdata) => {
1478                    ecx.journal_mut().checkpoint_commit();
1479                    Some(CallOutcome {
1480                        result: InterpreterResult {
1481                            result: InstructionResult::Return,
1482                            output: retdata.into(),
1483                            gas,
1484                        },
1485                        memory_offset: call.return_memory_offset.clone(),
1486                        was_precompile_called: true,
1487                        precompile_call_logs: vec![],
1488                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1489                    })
1490                }
1491                Err(err) => {
1492                    ecx.journal_mut().checkpoint_revert(checkpoint);
1493                    Some(CallOutcome {
1494                        result: InterpreterResult {
1495                            result: InstructionResult::Revert,
1496                            output: err.abi_encode().into(),
1497                            gas,
1498                        },
1499                        memory_offset: call.return_memory_offset.clone(),
1500                        was_precompile_called: false,
1501                        precompile_call_logs: vec![],
1502                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1503                    })
1504                }
1505            };
1506        }
1507
1508        if call.target_address == HARDHAT_CONSOLE_ADDRESS {
1509            return None;
1510        }
1511
1512        // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but
1513        // precompile calls don't create an interpreter frame so we must also track it here.
1514        // The callee executes at `curr_depth + 1`.
1515        if let Some(expected) = &mut self.expected_revert {
1516            expected.max_depth = max(curr_depth + 1, expected.max_depth);
1517        }
1518
1519        // Handle expected calls
1520
1521        // Grab the different calldatas expected.
1522        if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address)
1523        {
1524            let input = call.input.as_bytes(ecx);
1525            let value = call.transfer_value();
1526
1527            // Match every partial/full calldata
1528            for ((calldata, expected_scheme), (expected, actual_count)) in expected_calls_for_target
1529            {
1530                // Increment actual times seen if...
1531                // The calldata is at most, as big as this call's input, and
1532                if calldata.len() <= input.len() &&
1533                    // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and
1534                    input.get(..calldata.len()) == Some(calldata.as_ref()) &&
1535                    // The value matches, if provided
1536                    expected.value.is_none_or(|expected_value| Some(expected_value) == value) &&
1537                    // The gas matches, if provided
1538                    expected.gas.is_none_or(|gas| gas == call.gas_limit) &&
1539                    // The minimum gas matches, if provided
1540                    expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit) &&
1541                    // The call scheme matches, if provided
1542                    expected_scheme.is_none_or(|scheme| scheme == call.scheme)
1543                {
1544                    *actual_count += 1;
1545                }
1546            }
1547        }
1548
1549        // Apply our prank
1550        if let Some(prank) = &self.get_prank(curr_depth) {
1551            // Apply delegate call, `call.caller`` will not equal `prank.prank_caller`
1552            if prank.delegate_call
1553                && curr_depth == prank.depth
1554                && call.scheme == CallScheme::DelegateCall
1555            {
1556                call.target_address = prank.new_caller;
1557                call.caller = prank.new_caller;
1558                if let Some(new_origin) = prank.new_origin {
1559                    ecx.tx_mut().set_caller(new_origin);
1560                }
1561            }
1562
1563            if curr_depth >= prank.depth && call.caller == prank.prank_caller {
1564                // At the target depth we set `msg.sender`
1565                let prank_applied = if curr_depth == prank.depth {
1566                    // Ensure new caller is loaded and touched
1567                    let _ = journaled_account(ecx, prank.new_caller);
1568                    call.caller = prank.new_caller;
1569                    true
1570                } else {
1571                    false
1572                };
1573
1574                // At the target depth, or deeper, we set `tx.origin`
1575                let prank_applied = if let Some(new_origin) = prank.new_origin {
1576                    ecx.tx_mut().set_caller(new_origin);
1577                    true
1578                } else {
1579                    prank_applied
1580                };
1581
1582                // If prank applied for first time, then update
1583                if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1584                    self.pranks.insert(curr_depth, applied_prank);
1585                }
1586            }
1587        }
1588
1589        // Handle mocked calls
1590        if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
1591            let input = call.input.bytes(ecx);
1592            let value = call.transfer_value();
1593            let ctx = MockCallDataContext { calldata: input.clone(), value };
1594
1595            if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
1596                Some(queue) => Some(queue),
1597                None => mocks
1598                    .iter_mut()
1599                    .find(|(mock, _)| {
1600                        input.get(..mock.calldata.len()) == Some(&mock.calldata[..])
1601                            && mock.value.is_none_or(|mock_value| Some(mock_value) == value)
1602                    })
1603                    .map(|(_, v)| v),
1604            } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned())
1605            {
1606                if let Some(value) = call.transfer_value() {
1607                    let checkpoint = ecx.journal_mut().checkpoint();
1608                    match ecx.journal_mut().transfer_loaded(
1609                        call.transfer_from(),
1610                        call.transfer_to(),
1611                        value,
1612                    ) {
1613                        None => {
1614                            if return_data.ret_type.is_ok() {
1615                                ecx.journal_mut().checkpoint_commit();
1616                            } else {
1617                                ecx.journal_mut().checkpoint_revert(checkpoint);
1618                            }
1619                        }
1620                        Some(err) => {
1621                            ecx.journal_mut().checkpoint_revert(checkpoint);
1622                            return Some(CallOutcome {
1623                                result: InterpreterResult {
1624                                    result: err.into(),
1625                                    output: Bytes::new(),
1626                                    gas,
1627                                },
1628                                memory_offset: call.return_memory_offset.clone(),
1629                                was_precompile_called: false,
1630                                precompile_call_logs: vec![],
1631                                charged_new_account_state_gas: call.charged_new_account_state_gas,
1632                            });
1633                        }
1634                    }
1635                }
1636
1637                // If the mocked calls stack has a single element in it, don't empty it
1638                if return_data_queue.len() > 1 {
1639                    return_data_queue.pop_front();
1640                }
1641
1642                return Some(CallOutcome {
1643                    result: InterpreterResult {
1644                        result: return_data.ret_type,
1645                        output: return_data.data,
1646                        gas,
1647                    },
1648                    memory_offset: call.return_memory_offset.clone(),
1649                    was_precompile_called: true,
1650                    precompile_call_logs: vec![],
1651                    charged_new_account_state_gas: call.charged_new_account_state_gas,
1652                });
1653            }
1654        }
1655
1656        // Apply EIP-2930 access list
1657        self.apply_accesslist(ecx);
1658
1659        // Apply our broadcast
1660        if let Some(broadcast) = &self.broadcast {
1661            // Additional check as transfers in forge scripts seem to be estimated at 2300
1662            // by revm leading to "Intrinsic gas too low" failure when simulated on chain.
1663            let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
1664            self.dynamic_gas_limit = false;
1665
1666            // We only apply a broadcast *to a specific depth*.
1667            //
1668            // We do this because any subsequent contract calls *must* exist on chain and
1669            // we only want to grab *this* call, not internal ones
1670            if curr_depth == broadcast.depth && call.caller == broadcast.original_caller {
1671                // At the target depth we set `msg.sender` & tx.origin.
1672                // We are simulating the caller as being an EOA, so *both* must be set to the
1673                // broadcast.origin.
1674                ecx.tx_mut().set_caller(broadcast.new_origin);
1675
1676                call.caller = broadcast.new_origin;
1677                // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here
1678                // because we only need the from, to, value, and data. We can later change this
1679                // into 1559, in the cli package, relatively easily once we
1680                // know the target chain supports EIP-1559.
1681                if !call.is_static {
1682                    if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
1683                        return Some(CallOutcome {
1684                            result: InterpreterResult {
1685                                result: InstructionResult::Revert,
1686                                output: Error::encode(err),
1687                                gas,
1688                            },
1689                            memory_offset: call.return_memory_offset.clone(),
1690                            was_precompile_called: false,
1691                            precompile_call_logs: vec![],
1692                            charged_new_account_state_gas: call.charged_new_account_state_gas,
1693                        });
1694                    }
1695
1696                    let input = call.input.bytes(ecx);
1697                    let chain_id = ecx.cfg().chain_id();
1698                    let rpc = ecx.db().active_fork_url();
1699                    let fee_token = ecx.tx().fee_token();
1700                    let account =
1701                        ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap();
1702
1703                    let mut tx_req = TransactionRequestFor::<FEN>::default()
1704                        .with_from(broadcast.new_origin)
1705                        .with_to(call.target_address)
1706                        .with_value(call.transfer_value().unwrap_or_default())
1707                        .with_input(input)
1708                        .with_nonce(account.info.nonce)
1709                        .with_chain_id(chain_id);
1710                    if is_fixed_gas_limit {
1711                        tx_req.set_gas_limit(call.gas_limit)
1712                    }
1713
1714                    let active_delegations = std::mem::take(&mut self.active_delegations);
1715                    // Set active blob sidecar, if any.
1716                    if let Some(blob_sidecar) = self.active_blob_sidecar.take() {
1717                        // Ensure blob and delegation are not set for the same tx.
1718                        if !active_delegations.is_empty() {
1719                            let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
1720                            return Some(CallOutcome {
1721                                result: InterpreterResult {
1722                                    result: InstructionResult::Revert,
1723                                    output: Error::encode(msg),
1724                                    gas,
1725                                },
1726                                memory_offset: call.return_memory_offset.clone(),
1727                                was_precompile_called: false,
1728                                precompile_call_logs: vec![],
1729                                charged_new_account_state_gas: call.charged_new_account_state_gas,
1730                            });
1731                        }
1732                        tx_req.set_blob_sidecar(blob_sidecar);
1733                    }
1734
1735                    // Apply active EIP-7702 delegations, if any.
1736                    if !active_delegations.is_empty() {
1737                        for auth in &active_delegations {
1738                            let Ok(authority) = auth.recover_authority() else {
1739                                continue;
1740                            };
1741                            if authority == broadcast.new_origin {
1742                                // Increment nonce of broadcasting account to reflect signed
1743                                // authorization.
1744                                account.info.nonce += 1;
1745                            }
1746                        }
1747                        tx_req.set_authorization_list(active_delegations);
1748                    }
1749                    if let Some(fee_token) = fee_token {
1750                        tx_req.set_fee_token(fee_token);
1751                    }
1752                    self.broadcastable_transactions.push_back(BroadcastableTransaction {
1753                        rpc,
1754                        transaction: TransactionMaybeSigned::new(tx_req),
1755                    });
1756                    debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");
1757
1758                    // Isolated transactions increment the nonce during execution. Nested
1759                    // broadcasts do not start a separate transaction and need this increment.
1760                    if !isolate_call {
1761                        let prev = account.info.nonce;
1762                        account.info.nonce += 1;
1763                        debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce");
1764                    }
1765                } else if broadcast.single_call {
1766                    let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead";
1767                    return Some(CallOutcome {
1768                        result: InterpreterResult {
1769                            result: InstructionResult::Revert,
1770                            output: Error::encode(msg),
1771                            gas,
1772                        },
1773                        memory_offset: call.return_memory_offset.clone(),
1774                        was_precompile_called: false,
1775                        precompile_call_logs: vec![],
1776                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1777                    });
1778                }
1779            }
1780        }
1781
1782        // Record called accounts if `startStateDiffRecording` has been called
1783        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1784            // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero
1785            // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code
1786            let (initialized, old_balance, old_nonce) =
1787                if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) {
1788                    (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
1789                } else {
1790                    (false, U256::ZERO, 0)
1791                };
1792
1793            let kind = match call.scheme {
1794                CallScheme::Call => crate::Vm::AccountAccessKind::Call,
1795                CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
1796                CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
1797                CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall,
1798            };
1799
1800            // Record this call by pushing it to a new pending vector; all subsequent calls at
1801            // that depth will be pushed to the same vector. When the call ends, the
1802            // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be
1803            // updated with the revert status of this call, since the EVM does not mark accounts
1804            // as "warm" if the call from which they were accessed is reverted
1805            recorded_account_diffs_stack.push(vec![AccountAccess {
1806                chainInfo: crate::Vm::ChainInfo {
1807                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
1808                    chainId: U256::from(ecx.cfg().chain_id()),
1809                },
1810                accessor: call.caller,
1811                account: call.bytecode_address,
1812                kind,
1813                initialized,
1814                oldBalance: old_balance,
1815                newBalance: U256::ZERO, // updated on call_end
1816                oldNonce: old_nonce,
1817                newNonce: 0, // updated on call_end
1818                value: call.call_value(),
1819                data: call.input.bytes(ecx),
1820                reverted: false,
1821                deployedCode: Bytes::new(),
1822                storageAccesses: vec![], // updated on step
1823                depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1824            }]);
1825        }
1826
1827        None
1828    }
1829
1830    pub fn rng(&mut self) -> &mut impl Rng {
1831        self.test_runner().rng()
1832    }
1833
1834    pub fn test_runner(&mut self) -> &mut TestRunner {
1835        self.test_runner.get_or_insert_with(|| match self.config.seed {
1836            Some(seed) => TestRunner::new_with_rng(
1837                proptest::test_runner::Config::default(),
1838                TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1839            ),
1840            None => TestRunner::new(proptest::test_runner::Config::default()),
1841        })
1842    }
1843
1844    pub fn set_seed(&mut self, seed: U256) {
1845        self.test_runner = Some(TestRunner::new_with_rng(
1846            proptest::test_runner::Config::default(),
1847            TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1848        ));
1849    }
1850
1851    /// Returns existing or set a default `ArbitraryStorage` option.
1852    /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage.
1853    pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {
1854        self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default)
1855    }
1856
1857    /// Returns addresses explicitly marked with arbitrary storage.
1858    pub fn arbitrary_storage_targets(&self) -> impl Iterator<Item = Address> + '_ {
1859        self.arbitrary_storage.as_ref().into_iter().flat_map(ArbitraryStorage::targets)
1860    }
1861
1862    /// Returns addresses explicitly marked with arbitrary storage and whether nonzero slots are
1863    /// overwritten.
1864    pub fn arbitrary_storage_target_overwrite_modes(
1865        &self,
1866    ) -> impl Iterator<Item = (Address, bool)> + '_ {
1867        self.arbitrary_storage
1868            .as_ref()
1869            .into_iter()
1870            .flat_map(ArbitraryStorage::target_overwrite_modes)
1871    }
1872
1873    /// Returns addresses that copy storage from arbitrary-storage targets.
1874    pub fn arbitrary_storage_copied_targets(&self) -> impl Iterator<Item = Address> + '_ {
1875        self.arbitrary_storage.as_ref().into_iter().flat_map(ArbitraryStorage::copied_targets)
1876    }
1877
1878    /// Returns copied arbitrary-storage targets and their source address.
1879    pub fn arbitrary_storage_copied_target_sources(
1880        &self,
1881    ) -> impl Iterator<Item = (Address, Address)> + '_ {
1882        self.arbitrary_storage
1883            .as_ref()
1884            .into_iter()
1885            .flat_map(ArbitraryStorage::copied_target_sources)
1886    }
1887
1888    /// Caches a concrete replay value for a slot on an arbitrary-storage address or copied target.
1889    pub fn cache_arbitrary_storage_value(&mut self, address: Address, slot: U256, value: U256) {
1890        if let Some(storage) = &mut self.arbitrary_storage {
1891            storage.cache_value(address, slot, value);
1892        }
1893    }
1894
1895    /// Marks a slot as explicitly written with `vm.store`.
1896    pub fn mark_arbitrary_storage_slot_explicit(&mut self, address: Address, slot: U256) {
1897        if let Some(storage) = &mut self.arbitrary_storage {
1898            storage.mark_explicit(address, slot);
1899        }
1900    }
1901
1902    /// Returns whether a slot was explicitly written with `vm.store`.
1903    pub fn is_arbitrary_storage_slot_explicit(&self, address: Address, slot: U256) -> bool {
1904        self.arbitrary_storage.as_ref().is_some_and(|storage| storage.is_explicit(address, slot))
1905    }
1906
1907    /// Returns a cached arbitrary-storage replay value for a slot.
1908    pub fn cached_arbitrary_storage_value(&self, address: Address, slot: U256) -> Option<U256> {
1909        self.arbitrary_storage.as_ref().and_then(|storage| storage.cached_value(address, slot))
1910    }
1911
1912    /// Whether the given address has arbitrary storage.
1913    pub fn has_arbitrary_storage(&self, address: &Address) -> bool {
1914        match &self.arbitrary_storage {
1915            Some(storage) => storage.values.contains_key(address),
1916            None => false,
1917        }
1918    }
1919
1920    /// Whether the given slot of address with arbitrary storage should be overwritten.
1921    /// True if address is marked as and overwrite and if no value was previously generated for
1922    /// given slot.
1923    pub fn should_overwrite_arbitrary_storage(
1924        &self,
1925        address: &Address,
1926        storage_slot: U256,
1927    ) -> bool {
1928        match &self.arbitrary_storage {
1929            Some(storage) => {
1930                storage.overwrites.contains(address)
1931                    && storage
1932                        .values
1933                        .get(address)
1934                        .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot))
1935                        .is_none()
1936            }
1937            None => false,
1938        }
1939    }
1940
1941    /// Whether the given address is a copy of an address with arbitrary storage.
1942    pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool {
1943        match &self.arbitrary_storage {
1944            Some(storage) => storage.copies.contains_key(address),
1945            None => false,
1946        }
1947    }
1948
1949    /// Registers an SLOAD callback, replacing the existing callback for `target`.
1950    pub fn register_storage_load_hook(
1951        &mut self,
1952        target: Address,
1953        callback_target: Address,
1954        callback_selector: [u8; 4],
1955    ) {
1956        self.storage_load_hooks.insert(target, StorageHook { callback_target, callback_selector });
1957        self.storage_hooks_registered = true;
1958    }
1959
1960    /// Registers an SSTORE callback, replacing the existing callback for `target`.
1961    pub fn register_storage_store_hook(
1962        &mut self,
1963        target: Address,
1964        callback_target: Address,
1965        callback_selector: [u8; 4],
1966    ) {
1967        self.storage_store_hooks.insert(target, StorageHook { callback_target, callback_selector });
1968        self.storage_hooks_registered = true;
1969    }
1970
1971    /// Registers a mapping SSTORE callback. Returns false when a raw hook conflicts.
1972    pub fn register_mapping_storage_store_hook(
1973        &mut self,
1974        target: Address,
1975        root_slot: B256,
1976        callback_target: Address,
1977        callback_selector: [u8; 4],
1978    ) -> bool {
1979        if self.storage_store_hooks.contains_key(&target) {
1980            return false;
1981        }
1982        self.storage_hook_mapping_slots.remove(&target);
1983        self.mapping_storage_store_hooks
1984            .entry(target)
1985            .or_default()
1986            .insert(root_slot, StorageHook { callback_target, callback_selector });
1987        self.storage_hooks_registered = true;
1988        true
1989    }
1990
1991    /// Returns registered mapping SSTORE callbacks.
1992    pub fn mapping_storage_store_hooks(
1993        &self,
1994    ) -> impl Iterator<Item = (Address, B256, StorageHook)> + '_ {
1995        self.mapping_storage_store_hooks
1996            .iter()
1997            .flat_map(|(target, hooks)| hooks.iter().map(|(root, hook)| (*target, *root, *hook)))
1998    }
1999
2000    /// Returns whether mapping hooks conflict with a raw store hook.
2001    pub fn has_mapping_storage_store_hooks(&self, target: Address) -> bool {
2002        self.mapping_storage_store_hooks.get(&target).is_some_and(|hooks| !hooks.is_empty())
2003    }
2004
2005    /// Returns registered SLOAD callbacks.
2006    pub fn storage_load_hooks(&self) -> impl Iterator<Item = (Address, StorageHook)> + '_ {
2007        self.storage_load_hooks.iter().map(|(target, hook)| (*target, *hook))
2008    }
2009
2010    /// Returns registered SSTORE callbacks.
2011    pub fn storage_store_hooks(&self) -> impl Iterator<Item = (Address, StorageHook)> + '_ {
2012        self.storage_store_hooks.iter().map(|(target, hook)| (*target, *hook))
2013    }
2014
2015    /// Returns whether any storage callback is registered.
2016    #[inline]
2017    pub const fn has_storage_hooks(&self) -> bool {
2018        self.storage_hooks_registered
2019    }
2020
2021    /// Clears execution-local mapping provenance while preserving hook registrations.
2022    pub fn clear_storage_hook_mapping_slots(&mut self) {
2023        self.storage_hook_mapping_slots.clear();
2024    }
2025
2026    /// Returns whether a synthetic storage-hook callback or one of its child calls is executing.
2027    #[inline]
2028    pub const fn is_storage_hook_active(&self) -> bool {
2029        self.active_storage_hook.is_some()
2030    }
2031
2032    /// Returns whether `call` is the synthetic callback for the active storage hook.
2033    pub fn is_storage_hook_callback(
2034        &self,
2035        ecx: &FoundryContextFor<'_, FEN>,
2036        call: &CallInputs,
2037    ) -> bool {
2038        self.active_storage_hook.as_ref().is_some_and(|active| {
2039            active.outcome.is_none()
2040                && ecx.journal().depth() == active.parent_depth
2041                && call.caller == CHEATCODE_ADDRESS
2042                && call.target_address == active.callback_target
2043                && call.input.bytes(ecx) == active.callback_input
2044        })
2045    }
2046
2047    fn finish_storage_hook_call(
2048        &mut self,
2049        ecx: &FoundryContextFor<'_, FEN>,
2050        call: &CallInputs,
2051        outcome: &CallOutcome,
2052    ) -> bool {
2053        let Some(active) = self.active_storage_hook.as_mut() else { return false };
2054        if active.outcome.is_some()
2055            || ecx.journal().depth() != active.parent_depth
2056            || call.caller != CHEATCODE_ADDRESS
2057            || call.target_address != active.callback_target
2058            || call.input.bytes(ecx) != active.callback_input
2059        {
2060            return false;
2061        }
2062        active.outcome = Some((outcome.result.result, outcome.result.output.clone()));
2063        true
2064    }
2065
2066    #[inline(always)]
2067    pub fn has_step_hooks(&self) -> bool {
2068        self.broadcast.is_some()
2069            || self.gas_metering.paused
2070            || self.gas_metering.reset
2071            || self.recording_accesses
2072            || self.recorded_account_diffs_stack.is_some()
2073            || !self.allowed_mem_writes.is_empty()
2074            || self.mapping_slots.is_some()
2075            || self.gas_metering.recording
2076            || self.has_active_env_overrides()
2077            || self.has_storage_hooks()
2078    }
2079
2080    #[inline(always)]
2081    pub fn has_step_end_hooks(&self) -> bool {
2082        self.gas_metering.paused
2083            || self.gas_metering.touched
2084            || self.arbitrary_storage.is_some()
2085            || self.mapping_slots.is_some()
2086            || self.has_active_env_overrides()
2087            || self.has_storage_hooks()
2088    }
2089
2090    #[inline(always)]
2091    pub fn has_log_hooks(&self) -> bool {
2092        !self.expected_emits.is_empty() || self.recorded_logs.is_some()
2093    }
2094
2095    #[inline(always)]
2096    pub fn has_recording_accesses_only_step_hook(&self) -> bool {
2097        self.recording_accesses
2098            && self.broadcast.is_none()
2099            && !self.gas_metering.paused
2100            && !self.gas_metering.reset
2101            && self.recorded_account_diffs_stack.is_none()
2102            && self.allowed_mem_writes.is_empty()
2103            && self.mapping_slots.is_none()
2104            && !self.has_storage_hooks()
2105            && !self.gas_metering.recording
2106            && !self.has_active_env_overrides()
2107    }
2108
2109    #[inline(always)]
2110    fn has_active_env_overrides(&self) -> bool {
2111        self.env_overrides.values().any(EnvOverrides::is_any_set)
2112    }
2113
2114    /// Returns struct definitions from the analysis, if available.
2115    pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> {
2116        self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok())
2117    }
2118}
2119
2120const fn frame_gas(result: &InterpreterResult) -> Vm::Gas {
2121    let gas = &result.gas;
2122    // A halt consumes the regular gas restored while rolling back state gas.
2123    let regular_gas_spent = if result.is_halt() {
2124        gas.total_gas_spent()
2125    } else {
2126        gas.total_gas_spent().saturating_sub(gas.state_gas_spilled())
2127    };
2128    Vm::Gas {
2129        gasLimit: gas.limit(),
2130        gasTotalUsed: regular_gas_spent,
2131        gasMemoryUsed: 0,
2132        gasRefunded: gas.refunded(),
2133        gasRemaining: gas.remaining(),
2134        gasStateUsed: if result.is_ok() { gas.state_gas_spent() } else { 0 },
2135    }
2136}
2137
2138impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for Cheatcodes<FEN> {
2139    fn initialize_interp(
2140        &mut self,
2141        interpreter: &mut Interpreter,
2142        ecx: &mut FoundryContextFor<'_, FEN>,
2143    ) {
2144        // When the first interpreter is initialized we've circumvented the balance and gas checks,
2145        // so we apply our actual block data with the correct fees and all.
2146        if let Some(block) = self.block.take() {
2147            ecx.set_block(block);
2148        }
2149        if let Some(gas_price) = self.gas_price.take() {
2150            ecx.tx_mut().set_gas_price(gas_price);
2151        }
2152
2153        // Record gas for current frame.
2154        if self.gas_metering.paused {
2155            self.gas_metering.paused_frames.push(interpreter.gas);
2156        }
2157
2158        // `expectRevert`: track the max call depth during `expectRevert`
2159        if let Some(expected) = &mut self.expected_revert {
2160            expected.max_depth = max(ecx.journal().depth(), expected.max_depth);
2161        }
2162    }
2163
2164    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2165        self.pc = interpreter.bytecode.pc();
2166
2167        if !self.has_step_hooks() {
2168            return;
2169        }
2170
2171        if self.finish_storage_hook_callback(interpreter, ecx) {
2172            return;
2173        }
2174
2175        if self.broadcast.is_some() {
2176            self.set_gas_limit_type(interpreter);
2177        }
2178
2179        // Broadcasting changes outgoing calls, not the caller of the script's current frame.
2180        // Only protect the broadcasting frame; callbacks into the script have their own caller.
2181        if interpreter.bytecode.opcode() == op::CALLER
2182            && let Some(broadcast) = &self.broadcast
2183            && let Some(script_address) = self.script_address
2184            && ecx.journal().depth() == broadcast.depth
2185            && interpreter.input.target_address == script_address
2186            && interpreter.input.bytecode_address == Some(script_address)
2187            && interpreter.input.caller_address != broadcast.new_origin
2188        {
2189            interpreter.bytecode.set_action(InterpreterAction::new_return(
2190                InstructionResult::Revert,
2191                Bytes::from(
2192                    format!(
2193                        "Usage of `msg.sender` inside a `broadcast` in script contract detected. \
2194                         `msg.sender` is `{:#x}`, not the broadcast sender `{:#x}`. \
2195                         Use the `--sender` flag or pass the deployer address directly instead.",
2196                        interpreter.input.caller_address, broadcast.new_origin,
2197                    )
2198                    .into_bytes(),
2199                ),
2200                interpreter.gas,
2201            ));
2202            return;
2203        }
2204
2205        // `pauseGasMetering`: pause / resume interpreter gas.
2206        if self.gas_metering.paused {
2207            self.meter_gas(interpreter);
2208        }
2209
2210        // `resetGasMetering`: reset interpreter gas.
2211        if self.gas_metering.reset {
2212            self.meter_gas_reset(interpreter);
2213        }
2214
2215        // `record`: record storage reads and writes.
2216        if self.recording_accesses {
2217            self.record_accesses(interpreter);
2218        }
2219
2220        // `startStateDiffRecording`: record granular ordered storage accesses.
2221        if self.recorded_account_diffs_stack.is_some() {
2222            self.record_state_diffs(interpreter, ecx);
2223        }
2224
2225        // `expectSafeMemory`: check if the current opcode is allowed to interact with memory.
2226        if !self.allowed_mem_writes.is_empty() {
2227            self.check_mem_opcodes(
2228                interpreter,
2229                ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
2230            );
2231        }
2232
2233        if self.mapping_slots.is_some() || !self.mapping_storage_store_hooks.is_empty() {
2234            // `startMappingRecording`: record SSTORE.
2235            if let Some(mapping_slots) = &mut self.mapping_slots {
2236                mapping_step(mapping_slots, interpreter);
2237            }
2238
2239            let account = interpreter.input.target_address;
2240            let mapping_hook_active = self.active_storage_hook.is_none()
2241                && self
2242                    .mapping_storage_store_hooks
2243                    .get(&account)
2244                    .is_some_and(|hooks| !hooks.is_empty());
2245            if mapping_hook_active {
2246                mapping_step(&mut self.storage_hook_mapping_slots, interpreter);
2247            }
2248            self.pending_mapping_hash = if self.mapping_slots.is_some() || mapping_hook_active {
2249                capture_mapping_hash(interpreter)
2250            } else {
2251                None
2252            };
2253        }
2254
2255        // `snapshotGas*`: take a snapshot of the current gas.
2256        if self.gas_metering.recording {
2257            self.meter_gas_record(interpreter, ecx);
2258        }
2259
2260        // Capture the opcode for `step_end` to use, since by the time
2261        // `step_end` runs the PC has already advanced past it. Also peek the
2262        // BLOBHASH index now (still on top of stack before execution) so we
2263        // can look up the override later.
2264        if !self.env_overrides.is_empty() {
2265            let fork_id = ecx.db().active_fork_id();
2266            if let Some(env_overrides) =
2267                self.env_overrides.get_mut(&fork_id).filter(|o| o.is_any_set())
2268            {
2269                // Always clear stale pending state first so a leftover value from
2270                // a prior step (e.g. when `peek` failed, or when an override
2271                // wasn't actually used) cannot leak into the next opcode.
2272                env_overrides.pending_opcode = None;
2273                env_overrides.pending_blobhash_index = None;
2274
2275                let opcode = interpreter.bytecode.opcode();
2276                match opcode {
2277                    op::BASEFEE | op::GASPRICE => {
2278                        env_overrides.pending_opcode = Some(opcode);
2279                    }
2280                    op::BLOBHASH => {
2281                        env_overrides.pending_opcode = Some(opcode);
2282                        env_overrides.pending_blobhash_index =
2283                            interpreter.stack.peek(0).ok().and_then(|index| index.try_into().ok());
2284                    }
2285                    _ => {}
2286                }
2287            }
2288        }
2289
2290        if self.active_storage_hook.is_none() {
2291            self.capture_storage_hook(interpreter, ecx);
2292        }
2293    }
2294
2295    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2296        if !self.has_step_end_hooks() {
2297            return;
2298        }
2299
2300        if self.gas_metering.paused {
2301            self.meter_gas_end(interpreter);
2302        }
2303
2304        if self.gas_metering.touched {
2305            self.meter_gas_check(interpreter);
2306        }
2307
2308        // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage.
2309        if self.arbitrary_storage.is_some() {
2310            self.arbitrary_storage_end(interpreter, ecx);
2311        }
2312
2313        if let Some(pending) = self.pending_mapping_hash.take()
2314            && interpreter
2315                .bytecode
2316                .action
2317                .as_ref()
2318                .and_then(InterpreterAction::instruction_result)
2319                .is_none()
2320        {
2321            if let Some(mapping_slots) = &mut self.mapping_slots {
2322                record_mapping_hash(mapping_slots, interpreter, pending);
2323            }
2324            if self
2325                .mapping_storage_store_hooks
2326                .get(&pending.address)
2327                .is_some_and(|hooks| !hooks.is_empty())
2328                && self.active_storage_hook.is_none()
2329            {
2330                record_mapping_hash(&mut self.storage_hook_mapping_slots, interpreter, pending);
2331            }
2332        }
2333
2334        if self.active_storage_hook.is_none() {
2335            self.invoke_pending_storage_hook(interpreter, ecx);
2336        }
2337
2338        // Apply opcode-level env overrides (basefee/gasprice/blobhash). Needed
2339        // in isolation mode where the actual tx/block env is zeroed for
2340        // fee-accounting; in non-isolation mode the override and the real env
2341        // are kept in sync by the cheatcode handlers, so this is a no-op fixup.
2342        //
2343        // We must only rewrite the stack if the opcode actually completed
2344        // successfully and pushed its result; otherwise (stack underflow on
2345        // BLOBHASH, OOG before push, etc.) the stack is in an error state and
2346        // a blind `pop()+push()` would corrupt the failing frame.
2347        if !self.env_overrides.is_empty() {
2348            let fork_id = ecx.db().active_fork_id();
2349            if self.env_overrides.get(&fork_id).is_some_and(|o| o.is_any_set()) {
2350                // Mirrors the pattern used by `meter_gas_record`: when `action` is
2351                // `Some` with an `instruction_result`, the opcode has set a
2352                // non-continue result (halt/revert/error) — i.e. it didn't push
2353                // its normal result. `None` means "still running", which is the
2354                // success path for a stack-only opcode in `step_end`.
2355                let opcode_failed = interpreter
2356                    .bytecode
2357                    .action
2358                    .as_ref()
2359                    .and_then(|a| a.instruction_result())
2360                    .is_some();
2361                if opcode_failed {
2362                    if let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) {
2363                        env_overrides.pending_opcode = None;
2364                        env_overrides.pending_blobhash_index = None;
2365                    }
2366                } else {
2367                    self.apply_env_overrides(interpreter, fork_id);
2368                }
2369            }
2370        }
2371    }
2372
2373    fn log(&mut self, _ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2374        if !self.expected_emits.is_empty()
2375            && let Some(err) = expect::handle_expect_emit(self, &log, None)
2376        {
2377            // Because we do not have access to the interpreter here, we cannot fail the test
2378            // immediately. In most cases the failure will still be caught on `call_end`.
2379            // In the rare case it is not, we log the error here.
2380            let _ = sh_err!("{err:?}");
2381        }
2382
2383        // `recordLogs`
2384        record_logs(&mut self.recorded_logs, &log);
2385    }
2386
2387    fn log_full(
2388        &mut self,
2389        interpreter: &mut Interpreter,
2390        _ecx: &mut FoundryContextFor<'_, FEN>,
2391        log: Log,
2392    ) {
2393        if !self.expected_emits.is_empty() {
2394            expect::handle_expect_emit(self, &log, Some(interpreter));
2395        }
2396
2397        // `recordLogs`
2398        record_logs(&mut self.recorded_logs, &log);
2399    }
2400
2401    fn call(
2402        &mut self,
2403        ecx: &mut FoundryContextFor<'_, FEN>,
2404        inputs: &mut CallInputs,
2405    ) -> Option<CallOutcome> {
2406        if self.is_storage_hook_callback(ecx, inputs) {
2407            return None;
2408        }
2409        Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor, false)
2410    }
2411
2412    fn call_end(
2413        &mut self,
2414        ecx: &mut FoundryContextFor<'_, FEN>,
2415        call: &CallInputs,
2416        outcome: &mut CallOutcome,
2417    ) {
2418        let isolated_snapshot_gas_used = self.gas_metering.isolated_snapshot_gas_used.take();
2419        self.gas_metering.record_isolated_refund(
2420            ecx.journal().depth(),
2421            &outcome.result.gas,
2422            isolated_snapshot_gas_used,
2423        );
2424        if self.finish_storage_hook_call(ecx, call, outcome) {
2425            return;
2426        }
2427
2428        let cheatcode_call = call.target_address == CHEATCODE_ADDRESS
2429            || call.target_address == HARDHAT_CONSOLE_ADDRESS;
2430        #[cfg(feature = "monad")]
2431        let cheatcode_call = cheatcode_call
2432            || crate::monad::is_monad_cheatcode_call(
2433                self.extra_cheatcode_addresses,
2434                call.target_address,
2435            );
2436        let curr_depth = ecx.journal().depth();
2437
2438        self.finish_created_accounts_frame(
2439            outcome.result.is_ok(),
2440            CreatedAccountsFrameKind::Call,
2441            curr_depth,
2442        );
2443
2444        // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do
2445        // it for cheatcode calls because they are not applied for cheatcodes in the `call` hook.
2446        // This should be placed before the revert handling, because we might exit early there
2447        if !cheatcode_call {
2448            // Clean up pranks
2449            if let Some(prank) = &self.get_prank(curr_depth)
2450                && curr_depth == prank.depth
2451            {
2452                ecx.tx_mut().set_caller(prank.prank_origin);
2453
2454                // Clean single-call prank once we have returned to the original depth
2455                if prank.single_call {
2456                    self.pranks.remove(&curr_depth);
2457                }
2458            }
2459
2460            // Clean up broadcast
2461            if let Some(broadcast) = &self.broadcast
2462                && curr_depth == broadcast.depth
2463            {
2464                ecx.tx_mut().set_caller(broadcast.original_origin);
2465
2466                // Clean single-call broadcast once we have returned to the original depth
2467                if broadcast.single_call {
2468                    let _ = self.broadcast.take();
2469                }
2470            }
2471        }
2472
2473        // Handle assume no revert cheatcode.
2474        if let Some(assume_no_revert) = &mut self.assume_no_revert {
2475            // Record current reverter address before processing the expect revert if call reverted,
2476            // expect revert is set with expected reverter address and no actual reverter set yet.
2477            if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() {
2478                assume_no_revert.reverted_by = Some(call.target_address);
2479            }
2480
2481            // allow multiple cheatcode calls at the same depth
2482            let curr_depth = ecx.journal().depth();
2483            if curr_depth <= assume_no_revert.depth && !cheatcode_call {
2484                // Discard run if we're at the same depth as cheatcode, call reverted, and no
2485                // specific reason was supplied
2486                if outcome.result.is_revert() {
2487                    let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap();
2488                    return match revert_handlers::handle_assume_no_revert(
2489                        &assume_no_revert,
2490                        outcome.result.result,
2491                        &outcome.result.output,
2492                        &self.config.available_artifacts,
2493                    ) {
2494                        // if result is Ok, it was an anticipated revert; return an "assume" error
2495                        // to reject this run
2496                        Ok(_) => {
2497                            outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into();
2498                        }
2499                        // if result is Error, it was an unanticipated revert; should revert
2500                        // normally
2501                        Err(error) => {
2502                            trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
2503                            outcome.result.result = InstructionResult::Revert;
2504                            outcome.result.output = error.abi_encode().into();
2505                        }
2506                    };
2507                }
2508                // Call didn't revert, reset `assume_no_revert` state.
2509                self.assume_no_revert = None;
2510            }
2511        }
2512
2513        // Handle expected reverts.
2514        if let Some(expected_revert) = &mut self.expected_revert {
2515            // Record current reverter address and call scheme before processing the expect revert
2516            // if call reverted.
2517            let call_failed = !matches!(outcome.result.result, return_ok!());
2518            if call_failed {
2519                // Record current reverter address if expect revert is set with expected reverter
2520                // address and no actual reverter was set yet or if we're expecting more than one
2521                // revert.
2522                if expected_revert.reverter.is_some()
2523                    && (expected_revert.reverted_by.is_none() || expected_revert.count > 1)
2524                {
2525                    expected_revert.reverted_by = Some(call.target_address);
2526                }
2527            }
2528
2529            let curr_depth = ecx.journal().depth();
2530            if curr_depth <= expected_revert.depth {
2531                // Decide whether this `call_end` should consume the pending `expectRevert`.
2532                // With `internal_expect_revert` enabled, a same-depth revert can satisfy it, but
2533                // we must not consume it for external calls that succeed (e.g. calls to
2534                // non-contract addresses that return `Stop` before Solidity's own revert).
2535                let internal = self.config.internal_expect_revert;
2536                let went_deeper = expected_revert.max_depth > expected_revert.depth;
2537                let needs_processing = match expected_revert.kind {
2538                    ExpectedRevertKind::Default => (|| {
2539                        // Cheatcode reverts propagate up; let the outer frame catch them.
2540                        if cheatcode_call {
2541                            return false;
2542                        }
2543                        // Any failure satisfies the expectation.
2544                        if call_failed {
2545                            return true;
2546                        }
2547                        // Traditional expectRevert: succeeded external call went deeper.
2548                        if !internal && went_deeper {
2549                            return true;
2550                        }
2551                        // Test function returned: catch dangling expectations.
2552                        if curr_depth == 0 {
2553                            return true;
2554                        }
2555                        // Same-depth success with internal mode off is an error; with it on,
2556                        // keep waiting for the actual revert.
2557                        !internal
2558                    })(),
2559                    // `pending_processing == true` means we're in the `call_end` hook for
2560                    // `vm.expectCheatcodeRevert` and shouldn't expect a revert here.
2561                    ExpectedRevertKind::Cheatcode { pending_processing } => {
2562                        cheatcode_call && !pending_processing
2563                    }
2564                };
2565
2566                if needs_processing {
2567                    let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2568                    let clear_last_frame_gas =
2569                        matches!(expected_revert.kind, ExpectedRevertKind::Default);
2570                    return match revert_handlers::handle_expect_revert(
2571                        cheatcode_call,
2572                        false,
2573                        self.config.internal_expect_revert,
2574                        &expected_revert,
2575                        outcome.result.result,
2576                        outcome.result.output.clone(),
2577                        &self.config.available_artifacts,
2578                    ) {
2579                        Err(error) => {
2580                            trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
2581                            outcome.result.result = InstructionResult::Revert;
2582                            outcome.result.output = error.abi_encode().into();
2583                        }
2584                        Ok((_, retdata)) => {
2585                            expected_revert.actual_count += 1;
2586                            if expected_revert.actual_count < expected_revert.count {
2587                                self.expected_revert = Some(expected_revert);
2588                            }
2589                            if clear_last_frame_gas {
2590                                self.gas_metering.last_frame_gas = None;
2591                            }
2592                            outcome.result.result = InstructionResult::Return;
2593                            outcome.result.output = retdata;
2594                        }
2595                    };
2596                }
2597
2598                // Flip `pending_processing` flag for cheatcode revert expectations, marking that
2599                // we've exited the `expectCheatcodeRevert` call scope
2600                if let ExpectedRevertKind::Cheatcode { pending_processing } =
2601                    &mut self.expected_revert.as_mut().unwrap().kind
2602                {
2603                    *pending_processing = false;
2604                }
2605            }
2606        }
2607
2608        // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode
2609        // invocations
2610        if cheatcode_call {
2611            return;
2612        }
2613
2614        // Record the gas usage of the call, this allows the `lastFrameGas` cheatcode to
2615        // retrieve the gas usage of the last call or create.
2616        let frame_gas = frame_gas(&outcome.result);
2617        let snapshot_gas_used =
2618            isolated_snapshot_gas_used.unwrap_or_else(|| outcome.result.gas.total_gas_spent());
2619        self.gas_metering.last_call_gas = Some(frame_gas.clone());
2620        self.gas_metering.last_frame_gas = Some(frame_gas);
2621        self.gas_metering.last_call_snapshot_gas_used = snapshot_gas_used;
2622        self.gas_metering.last_frame_snapshot_gas_used = snapshot_gas_used;
2623
2624        // If `startStateDiffRecording` has been called, update the `reverted` status of the
2625        // previous call depth's recorded accesses, if any
2626        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2627            // The root call cannot be recorded.
2628            if ecx.journal().depth() > 0
2629                && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop()
2630            {
2631                // Update the reverted status of all deeper calls if this call reverted, in
2632                // accordance with EVM behavior
2633                if outcome.result.is_revert() {
2634                    for element in &mut *last_recorded_depth {
2635                        element.reverted = true;
2636                        for storage_access in &mut element.storageAccesses {
2637                            storage_access.reverted = true;
2638                        }
2639                    }
2640                }
2641
2642                if let Some(call_access) = last_recorded_depth.first_mut() {
2643                    // Assert that we're at the correct depth before recording post-call state
2644                    // changes. Depending on the depth the cheat was
2645                    // called at, there may not be any pending
2646                    // calls to update if execution has percolated up to a higher depth.
2647                    let curr_depth = ecx.journal().depth();
2648                    if call_access.depth == curr_depth as u64
2649                        && let Ok(acc) = ecx.journal_mut().load_account(call.target_address)
2650                    {
2651                        debug_assert!(access_is_call(call_access.kind));
2652                        call_access.newBalance = acc.data.info.balance;
2653                        call_access.newNonce = acc.data.info.nonce;
2654                    }
2655                    // Merge the last depth's AccountAccesses into the AccountAccesses at the
2656                    // current depth, or push them back onto the pending
2657                    // vector if higher depths were not recorded. This
2658                    // preserves ordering of accesses.
2659                    if let Some(last) = recorded_account_diffs_stack.last_mut() {
2660                        last.extend(last_recorded_depth);
2661                    } else {
2662                        recorded_account_diffs_stack.push(last_recorded_depth);
2663                    }
2664                }
2665            }
2666        }
2667
2668        // this will ensure we don't have false positives when trying to diagnose reverts in fork
2669        // mode
2670        let diag = self.fork_revert_diagnostic.take();
2671
2672        // If the call already reverted, preserve that primary failure and skip post-call
2673        // expect* validation so it cannot overwrite the original revert.
2674        if outcome.result.is_revert() {
2675            // if there's a revert and a previous call was diagnosed as fork related revert then we
2676            // can return a better error here
2677            if let Some(err) = diag {
2678                outcome.result.output = Error::encode(err.to_error_msg(&self.labels));
2679            }
2680            return;
2681        }
2682
2683        // At the end of the call,
2684        // we need to check if we've found all the emits.
2685        // We know we've found all the expected emits in the right order
2686        // if the queue is fully matched.
2687        // If it's not fully matched, then either:
2688        // 1. Not enough events were emitted (we'll know this because the amount of times we
2689        // inspected events will be less than the size of the queue) 2. The wrong events
2690        // were emitted (The inspected events should match the size of the queue, but still some
2691        // events will not be matched)
2692
2693        // First, check that we're at the call depth where the emits were declared from.
2694        let should_check_emits = self
2695            .expected_emits
2696            .iter()
2697            .any(|(expected, _)| {
2698                let curr_depth = ecx.journal().depth();
2699                expected.depth == curr_depth
2700            }) &&
2701            // Ignore staticcalls
2702            !call.is_static;
2703        if should_check_emits {
2704            let expected_counts = self
2705                .expected_emits
2706                .iter()
2707                .filter_map(|(expected, count_map)| {
2708                    let count = match expected.address {
2709                        Some(emitter) => match count_map.get(&emitter) {
2710                            Some(log_count) => expected
2711                                .log
2712                                .as_ref()
2713                                .map(|l| log_count.count(l))
2714                                .unwrap_or_else(|| log_count.count_unchecked()),
2715                            None => 0,
2716                        },
2717                        None => match &expected.log {
2718                            Some(log) => count_map.values().map(|logs| logs.count(log)).sum(),
2719                            None => count_map.values().map(|logs| logs.count_unchecked()).sum(),
2720                        },
2721                    };
2722
2723                    (count != expected.count).then_some((expected, count))
2724                })
2725                .collect::<Vec<_>>();
2726
2727            // Revert if not all emits expected were matched.
2728            if let Some((expected, _)) = self
2729                .expected_emits
2730                .iter()
2731                .find(|(expected, _)| !expected.found && expected.count > 0)
2732            {
2733                outcome.result.result = InstructionResult::Revert;
2734                let mismatch_error = expected.mismatch_error.clone();
2735                let expected_log = expected.log.clone();
2736                let checks = expected.checks;
2737                let anonymous = expected.anonymous;
2738                let error_msg = mismatch_error
2739                    .as_ref()
2740                    .map(|mismatch| {
2741                        mismatch.to_error_msg(self, checks, expected_log.as_ref(), anonymous)
2742                    })
2743                    .unwrap_or_else(|| "log != expected log".to_string());
2744                outcome.result.output = error_msg.abi_encode().into();
2745                return;
2746            }
2747
2748            if !expected_counts.is_empty() {
2749                let msg = if outcome.result.is_ok() {
2750                    let (expected, count) = expected_counts.first().unwrap();
2751                    format!("log emitted {count} times, expected {}", expected.count)
2752                } else {
2753                    "expected an emit, but the call reverted instead. \
2754                     ensure you're testing the happy path when using `expectEmit`"
2755                        .to_string()
2756                };
2757
2758                outcome.result.result = InstructionResult::Revert;
2759                outcome.result.output = Error::encode(msg);
2760                return;
2761            }
2762
2763            // All emits were found, we're good.
2764            // Clear the queue, as we expect the user to declare more events for the next call
2765            // if they wanna match further events.
2766            self.expected_emits.clear()
2767        }
2768
2769        // try to diagnose reverts in multi-fork mode where a call is made to an address that does
2770        // not exist
2771        if let TxKind::Call(test_contract) = ecx.tx().kind() {
2772            // if a call to a different contract than the original test contract returned with
2773            // `Stop` we check if the contract actually exists on the active fork
2774            if ecx.db().is_forked_mode()
2775                && outcome.result.result == InstructionResult::Stop
2776                && call.target_address != test_contract
2777            {
2778                self.fork_revert_diagnostic =
2779                    ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state());
2780            }
2781        }
2782
2783        // If the depth is 0, then this is the root call terminating
2784        if ecx.journal().depth() == 0 {
2785            // If we already have a revert, we shouldn't run the below logic as it can obfuscate an
2786            // earlier error that happened first with unrelated information about
2787            // another error when using cheatcodes.
2788            if outcome.result.is_revert() {
2789                return;
2790            }
2791
2792            // If there's not a revert, we can continue on to run the last logic for expect*
2793            // cheatcodes.
2794
2795            // Match expected calls
2796            for (address, calldatas) in &self.expected_calls {
2797                // Loop over each address, and for each address, loop over each calldata it expects.
2798                for ((calldata, scheme), (expected, actual_count)) in calldatas {
2799                    // Grab the values we expect to see
2800                    let ExpectedCallData { gas, min_gas, value, count, call_type } = expected;
2801
2802                    let failed = match call_type {
2803                        // If the cheatcode was called with a `count` argument,
2804                        // we must check that the EVM performed a CALL with this calldata exactly
2805                        // `count` times.
2806                        ExpectedCallType::Count => *count != *actual_count,
2807                        // If the cheatcode was called without a `count` argument,
2808                        // we must check that the EVM performed a CALL with this calldata at least
2809                        // `count` times. The amount of times to check was
2810                        // the amount of time the cheatcode was called.
2811                        ExpectedCallType::NonCount => *count > *actual_count,
2812                    };
2813                    if failed {
2814                        let expected_values = [
2815                            Some(format!("data {}", hex::encode_prefixed(calldata))),
2816                            value.as_ref().map(|v| format!("value {v}")),
2817                            gas.map(|g| format!("gas {g}")),
2818                            min_gas.map(|g| format!("minimum gas {g}")),
2819                            scheme.map(|scheme| format!("call type {scheme:?}")),
2820                        ]
2821                        .into_iter()
2822                        .flatten()
2823                        .join(", ");
2824                        let but = if outcome.result.is_ok() {
2825                            let s = if *actual_count == 1 { "" } else { "s" };
2826                            format!("was called {actual_count} time{s}")
2827                        } else {
2828                            "the call reverted instead; \
2829                             ensure you're testing the happy path when using `expectCall`"
2830                                .to_string()
2831                        };
2832                        let s = if *count == 1 { "" } else { "s" };
2833                        let msg = format!(
2834                            "expected call to {address} with {expected_values} \
2835                             to be called {count} time{s}, but {but}"
2836                        );
2837                        outcome.result.result = InstructionResult::Revert;
2838                        outcome.result.output = Error::encode(msg);
2839
2840                        return;
2841                    }
2842                }
2843            }
2844
2845            // Check if we have any leftover expected emits
2846            // First, if any emits were found at the root call, then we its ok and we remove them.
2847            // For count=0 expectations, NOT being found is success, so mark them as found
2848            for (expected, _) in &mut self.expected_emits {
2849                if expected.count == 0 && !expected.found {
2850                    expected.found = true;
2851                }
2852            }
2853            self.expected_emits.retain(|(expected, _)| !expected.found);
2854            // If not empty, we got mismatched emits
2855            if !self.expected_emits.is_empty() {
2856                let msg = if outcome.result.is_ok() {
2857                    "expected an emit, but no logs were emitted afterwards. \
2858                     you might have mismatched events or not enough events were emitted"
2859                } else {
2860                    "expected an emit, but the call reverted instead. \
2861                     ensure you're testing the happy path when using `expectEmit`"
2862                };
2863                outcome.result.result = InstructionResult::Revert;
2864                outcome.result.output = Error::encode(msg);
2865                return;
2866            }
2867
2868            // Check for leftover expected creates
2869            if let Some(expected_create) = self.expected_creates.first() {
2870                let msg = format!(
2871                    "expected {} call by address {} for bytecode {} but not found",
2872                    expected_create.create_scheme,
2873                    hex::encode_prefixed(expected_create.deployer),
2874                    hex::encode_prefixed(&expected_create.bytecode),
2875                );
2876                outcome.result.result = InstructionResult::Revert;
2877                outcome.result.output = Error::encode(msg);
2878            }
2879        }
2880    }
2881
2882    fn create(
2883        &mut self,
2884        ecx: &mut FoundryContextFor<'_, FEN>,
2885        mut input: &mut CreateInputs,
2886    ) -> Option<CreateOutcome> {
2887        // Apply custom execution evm version.
2888        if let Some(spec_id) = self.execution_evm_version {
2889            ecx.set_spec_and_gas_params(spec_id);
2890        }
2891
2892        let gas = Gas::new(input.gas_limit());
2893        let curr_depth = ecx.journal().depth();
2894        self.start_created_accounts_frame(
2895            curr_depth == 0,
2896            CreatedAccountsFrameKind::Create,
2897            curr_depth,
2898        );
2899
2900        // Check if we should intercept this create
2901        if self.intercept_next_create_call {
2902            // Reset the flag
2903            self.intercept_next_create_call = false;
2904
2905            // Get initcode from the input
2906            let output = input.init_code();
2907
2908            // Return a revert with the initcode as error data
2909            return Some(CreateOutcome {
2910                result: InterpreterResult { result: InstructionResult::Revert, output, gas },
2911                address: None,
2912                charged_create_state_gas: input.charged_create_state_gas(),
2913            });
2914        }
2915
2916        // Apply our prank
2917        if let Some(prank) = &self.get_prank(curr_depth)
2918            && curr_depth >= prank.depth
2919            && input.caller() == prank.prank_caller
2920        {
2921            // At the target depth we set `msg.sender`
2922            let prank_applied = if curr_depth == prank.depth {
2923                // Ensure new caller is loaded and touched
2924                let _ = journaled_account(ecx, prank.new_caller);
2925                input.set_caller(prank.new_caller);
2926                true
2927            } else {
2928                false
2929            };
2930
2931            // At the target depth, or deeper, we set `tx.origin`
2932            let prank_applied = if let Some(new_origin) = prank.new_origin {
2933                ecx.tx_mut().set_caller(new_origin);
2934                true
2935            } else {
2936                prank_applied
2937            };
2938
2939            // If prank applied for first time, then update
2940            if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
2941                self.pranks.insert(curr_depth, applied_prank);
2942            }
2943        }
2944
2945        // Apply EIP-2930 access list
2946        self.apply_accesslist(ecx);
2947
2948        // Apply our broadcast
2949        if let Some(broadcast) = &mut self.broadcast
2950            && curr_depth >= broadcast.depth
2951            && input.caller() == broadcast.original_caller
2952        {
2953            if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
2954                return Some(CreateOutcome {
2955                    result: InterpreterResult {
2956                        result: InstructionResult::Revert,
2957                        output: Error::encode(err),
2958                        gas,
2959                    },
2960                    address: None,
2961                    charged_create_state_gas: input.charged_create_state_gas(),
2962                });
2963            }
2964
2965            ecx.tx_mut().set_caller(broadcast.new_origin);
2966
2967            if curr_depth == broadcast.depth || broadcast.deploy_from_code {
2968                // Reset deploy from code flag for upcoming calls;
2969                broadcast.deploy_from_code = false;
2970
2971                input.set_caller(broadcast.new_origin);
2972
2973                let rpc = ecx.db().active_fork_url();
2974                let fee_token = ecx.tx().fee_token();
2975                let account = &ecx.journal().evm_state()[&broadcast.new_origin];
2976                let mut tx_req = TransactionRequestFor::<FEN>::default()
2977                    .with_from(broadcast.new_origin)
2978                    .with_kind(TxKind::Create)
2979                    .with_value(input.value())
2980                    .with_input(input.init_code())
2981                    .with_nonce(account.info.nonce);
2982                if let Some(fee_token) = fee_token {
2983                    tx_req.set_fee_token(fee_token);
2984                }
2985                self.broadcastable_transactions.push_back(BroadcastableTransaction {
2986                    rpc,
2987                    transaction: TransactionMaybeSigned::new(tx_req),
2988                });
2989
2990                input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create));
2991            }
2992        }
2993
2994        // Allow cheatcodes from the address of the new contract
2995        let address = input.allow_cheatcodes(self, ecx);
2996
2997        self.record_created_account(ecx.db().active_fork_id(), address);
2998
2999        // If `recordAccountAccesses` has been called, record the create
3000        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
3001            recorded_account_diffs_stack.push(vec![AccountAccess {
3002                chainInfo: crate::Vm::ChainInfo {
3003                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
3004                    chainId: U256::from(ecx.cfg().chain_id()),
3005                },
3006                accessor: input.caller(),
3007                account: address,
3008                kind: crate::Vm::AccountAccessKind::Create,
3009                initialized: true,
3010                oldBalance: U256::ZERO, // updated on create_end
3011                newBalance: U256::ZERO, // updated on create_end
3012                oldNonce: 0,            // new contract starts with nonce 0
3013                newNonce: 1,            // updated on create_end (contracts start with nonce 1)
3014                value: input.value(),
3015                data: input.init_code(),
3016                reverted: false,
3017                deployedCode: Bytes::new(), // updated on create_end
3018                storageAccesses: vec![],    // updated on create_end
3019                depth: curr_depth as u64,
3020            }]);
3021        }
3022
3023        None
3024    }
3025
3026    fn create_end(
3027        &mut self,
3028        ecx: &mut FoundryContextFor<'_, FEN>,
3029        call: &CreateInputs,
3030        outcome: &mut CreateOutcome,
3031    ) {
3032        let isolated_snapshot_gas_used = self.gas_metering.isolated_snapshot_gas_used.take();
3033        self.gas_metering.record_isolated_refund(
3034            ecx.journal().depth(),
3035            &outcome.result.gas,
3036            isolated_snapshot_gas_used,
3037        );
3038        let call = Some(call);
3039        let curr_depth = ecx.journal().depth();
3040
3041        self.finish_created_accounts_frame(
3042            outcome.result.is_ok(),
3043            CreatedAccountsFrameKind::Create,
3044            curr_depth,
3045        );
3046
3047        // Clean up pranks
3048        if let Some(prank) = &self.get_prank(curr_depth)
3049            && curr_depth == prank.depth
3050        {
3051            ecx.tx_mut().set_caller(prank.prank_origin);
3052
3053            // Clean single-call prank once we have returned to the original depth
3054            if prank.single_call {
3055                std::mem::take(&mut self.pranks);
3056            }
3057        }
3058
3059        // Clean up broadcasts
3060        if let Some(broadcast) = &self.broadcast
3061            && curr_depth == broadcast.depth
3062        {
3063            ecx.tx_mut().set_caller(broadcast.original_origin);
3064
3065            // Clean single-call broadcast once we have returned to the original depth
3066            if broadcast.single_call {
3067                std::mem::take(&mut self.broadcast);
3068            }
3069        }
3070
3071        // Handle expected reverts.
3072        if let Some(expected_revert) = &mut self.expected_revert {
3073            // Record the would-be deployed address as the reverter, picking the innermost
3074            // reverting CREATE: this hook runs at every depth, the deepest frame fires
3075            // first, and the `is_none()` lock pins it. For `count > 1` the lock is
3076            // released after each successful iteration (see below) so each iteration
3077            // independently records its own innermost CREATE.
3078            //
3079            // This intentionally differs from `call_end` for `count > 1`, where
3080            // legacy nested CALL handling reports the outermost call per iteration.
3081            //
3082            // `outcome.address` is `None` for pre-frame rejection (depth/balance/nonce);
3083            // in that case the surrounding `call_end` records the caller as the reverter.
3084            if outcome.result.is_revert()
3085                && expected_revert.reverter.is_some()
3086                && expected_revert.reverted_by.is_none()
3087                && let Some(addr) = outcome.address
3088            {
3089                expected_revert.reverted_by = Some(addr);
3090            }
3091
3092            if curr_depth <= expected_revert.depth
3093                && matches!(expected_revert.kind, ExpectedRevertKind::Default)
3094            {
3095                let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
3096                return match revert_handlers::handle_expect_revert(
3097                    false,
3098                    true,
3099                    self.config.internal_expect_revert,
3100                    &expected_revert,
3101                    outcome.result.result,
3102                    outcome.result.output.clone(),
3103                    &self.config.available_artifacts,
3104                ) {
3105                    Ok((address, retdata)) => {
3106                        expected_revert.actual_count += 1;
3107                        if expected_revert.actual_count < expected_revert.count {
3108                            // Reset so the next iteration's innermost CREATE wins again.
3109                            expected_revert.reverted_by = None;
3110                            self.expected_revert = Some(expected_revert.clone());
3111                        }
3112
3113                        outcome.result.result = InstructionResult::Return;
3114                        outcome.result.output = retdata;
3115                        outcome.address = address;
3116                        self.gas_metering.last_frame_gas = None;
3117                    }
3118                    Err(err) => {
3119                        outcome.result.result = InstructionResult::Revert;
3120                        outcome.result.output = err.abi_encode().into();
3121                    }
3122                };
3123            }
3124        }
3125
3126        if curr_depth > 0 {
3127            // Record the gas usage of the create frame, this allows the `lastFrameGas` cheatcode to
3128            // retrieve the gas usage of the last call or create.
3129            self.gas_metering.last_frame_gas = Some(frame_gas(&outcome.result));
3130            self.gas_metering.last_frame_snapshot_gas_used =
3131                isolated_snapshot_gas_used.unwrap_or_else(|| outcome.result.gas.total_gas_spent());
3132        }
3133
3134        // If `startStateDiffRecording` has been called, update the `reverted` status of the
3135        // previous call depth's recorded accesses, if any.
3136        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack
3137            && let Some(mut last_depth) = recorded_account_diffs_stack.pop()
3138        {
3139            // Update the reverted status of all deeper calls if this call reverted, in
3140            // accordance with EVM behavior.
3141            if outcome.result.is_revert() {
3142                for element in &mut *last_depth {
3143                    element.reverted = true;
3144                    for storage_access in &mut element.storageAccesses {
3145                        storage_access.reverted = true;
3146                    }
3147                }
3148            }
3149
3150            if let Some(create_access) = last_depth.first_mut() {
3151                // Update post-create state only if recording began before this frame.
3152                if create_access.depth == curr_depth as u64 {
3153                    debug_assert_eq!(
3154                        create_access.kind as u8,
3155                        crate::Vm::AccountAccessKind::Create as u8
3156                    );
3157                    if let Some(address) = outcome.address
3158                        && let Ok(created_acc) = ecx.journal_mut().load_account(address)
3159                    {
3160                        create_access.newBalance = created_acc.data.info.balance;
3161                        create_access.newNonce = created_acc.data.info.nonce;
3162                        create_access.deployedCode =
3163                            created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
3164                    }
3165                }
3166            }
3167            // Merge the last depth's AccountAccesses into the AccountAccesses at the
3168            // current depth, or push them back onto the pending
3169            // vector if higher depths were not recorded. This
3170            // preserves ordering of accesses.
3171            if let Some(last) = recorded_account_diffs_stack.last_mut() {
3172                last.append(&mut last_depth);
3173            } else {
3174                recorded_account_diffs_stack.push(last_depth);
3175            }
3176        }
3177
3178        // Match the create against expected_creates
3179        if !self.expected_creates.is_empty()
3180            && let (Some(address), Some(call)) = (outcome.address, call)
3181            && let Ok(created_acc) = ecx.journal_mut().load_account(address)
3182        {
3183            let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
3184            if let Some((index, _)) =
3185                self.expected_creates.iter().find_position(|expected_create| {
3186                    expected_create.deployer == call.caller()
3187                        && expected_create.create_scheme.eq(call.scheme().into())
3188                        && expected_create.bytecode == bytecode
3189                })
3190            {
3191                self.expected_creates.swap_remove(index);
3192            }
3193        }
3194    }
3195}
3196
3197impl<FEN: FoundryEvmNetwork> InspectorExt for Cheatcodes<FEN> {
3198    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
3199        let target_depth = if let Some(prank) = &self.get_prank(depth) {
3200            prank.depth
3201        } else if let Some(broadcast) = &self.broadcast {
3202            broadcast.depth
3203        } else {
3204            1
3205        };
3206
3207        if depth != target_depth {
3208            return false;
3209        }
3210
3211        match inputs.scheme() {
3212            CreateScheme::Create2 { .. } => {
3213                self.broadcast.is_some() || self.config.always_use_create_2_factory
3214            }
3215            CreateScheme::Create => self.config.batch_rewrite_creates && self.broadcast.is_some(),
3216            _ => false,
3217        }
3218    }
3219
3220    fn create2_deployer(&self) -> Address {
3221        self.config.evm_opts.create2_deployer
3222    }
3223}
3224
3225impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
3226    #[cold]
3227    fn meter_gas(&mut self, interpreter: &mut Interpreter) {
3228        if let Some(paused_gas) = self.gas_metering.paused_frames.last() {
3229            // Keep gas constant if paused.
3230            // Make sure we record the memory changes so that memory expansion is not paused.
3231            let memory = *interpreter.gas.memory();
3232            interpreter.gas = *paused_gas;
3233            interpreter.gas.memory_mut().words_num = memory.words_num;
3234            interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost;
3235        } else {
3236            // Record frame paused gas.
3237            self.gas_metering.paused_frames.push(interpreter.gas);
3238        }
3239    }
3240
3241    #[cold]
3242    fn meter_gas_record(
3243        &mut self,
3244        interpreter: &mut Interpreter,
3245        ecx: &mut FoundryContextFor<'_, FEN>,
3246    ) {
3247        if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() {
3248            let curr_depth = ecx.journal().depth();
3249            let isolated_refund = match self.gas_metering.pending_isolated_refund {
3250                Some((depth, refund)) if depth == curr_depth => {
3251                    self.gas_metering.pending_isolated_refund = None;
3252                    refund
3253                }
3254                _ => 0,
3255            };
3256            self.gas_metering.gas_records.iter_mut().for_each(|record| {
3257                if curr_depth == record.depth {
3258                    // Skip the first opcode of the first call frame as it includes the gas cost of
3259                    // creating the snapshot.
3260                    if self.gas_metering.last_gas_used != 0 {
3261                        let gas_diff = interpreter
3262                            .gas
3263                            .total_gas_spent()
3264                            .saturating_sub(self.gas_metering.last_gas_used)
3265                            .saturating_sub(isolated_refund);
3266                        record.gas_used = record.gas_used.saturating_add(gas_diff);
3267                    }
3268
3269                    // Update `last_gas_used` to the current spent gas for the next iteration to
3270                    // compare against.
3271                    self.gas_metering.last_gas_used = interpreter.gas.total_gas_spent();
3272                }
3273            });
3274        }
3275    }
3276
3277    #[cold]
3278    fn meter_gas_end(&mut self, interpreter: &mut Interpreter) {
3279        // Remove recorded gas if we exit frame.
3280        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
3281            && will_exit(interpreter_action)
3282        {
3283            self.gas_metering.paused_frames.pop();
3284        }
3285    }
3286
3287    #[cold]
3288    const fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) {
3289        let mut gas = Gas::new(interpreter.gas.limit());
3290        gas.memory_mut().words_num = interpreter.gas.memory().words_num;
3291        gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost;
3292        interpreter.gas = gas;
3293        self.gas_metering.reset = false;
3294    }
3295
3296    #[cold]
3297    fn meter_gas_check(&mut self, interpreter: &mut Interpreter) {
3298        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
3299            && will_exit(interpreter_action)
3300        {
3301            // Reset gas if spent is less than refunded.
3302            // This can happen if gas was paused / resumed or reset.
3303            // https://github.com/foundry-rs/foundry/issues/4370
3304            if interpreter.gas.total_gas_spent()
3305                < u64::try_from(interpreter.gas.refunded()).unwrap_or_default()
3306            {
3307                interpreter.gas = Gas::new(interpreter.gas.limit());
3308            }
3309        }
3310    }
3311
3312    /// Applies opcode-level overrides for `BASEFEE`, `GASPRICE` and `BLOBHASH`.
3313    ///
3314    /// Called from `step_end` *after* the opcode has executed and only when the
3315    /// opcode succeeded (the caller checks `instruction_result`). The opcode
3316    /// pushed its (possibly zeroed) result onto the stack; we replace the top
3317    /// of stack with the cheatcode-set override. This is what makes `vm.fee`,
3318    /// `vm.txGasPrice` and `vm.blobhashes` visible to called contracts under
3319    /// `--isolate` / `--gas-report`, where the inner transaction zeroes the
3320    /// real fee fields for fee-accounting purposes.
3321    ///
3322    /// We can't read the just-executed opcode from `interpreter.bytecode.opcode()`
3323    /// here because the PC has already advanced; instead `step` stashes it in
3324    /// `env_overrides.pending_opcode` for us.
3325    #[cold]
3326    fn apply_env_overrides(&mut self, interpreter: &mut Interpreter, fork_id: Option<U256>) {
3327        let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) else { return };
3328        let Some(opcode) = env_overrides.pending_opcode.take() else { return };
3329        match opcode {
3330            op::BASEFEE => {
3331                if let Some(basefee) = env_overrides.basefee {
3332                    // BASEFEE pushed one value; replace it.
3333                    Self::replace_top_of_stack(interpreter, U256::from(basefee));
3334                }
3335            }
3336            op::GASPRICE => {
3337                if let Some(gas_price) = env_overrides.gas_price {
3338                    // GASPRICE pushed one value; replace it.
3339                    Self::replace_top_of_stack(interpreter, U256::from(gas_price));
3340                }
3341            }
3342            op::BLOBHASH => {
3343                let blob_hashes = env_overrides.blob_hashes.clone();
3344                let blobhash_index = env_overrides.pending_blobhash_index.take();
3345                if let Some(ref blob_hashes) = blob_hashes
3346                    && let Some(index) = blobhash_index
3347                {
3348                    // BLOBHASH popped the index and pushed the hash; replace
3349                    // the hash with our override (zero for out-of-range, per EIP-4844).
3350                    let hash = blob_hashes.get(index as usize).copied().unwrap_or_default();
3351                    Self::replace_top_of_stack(interpreter, hash.into());
3352                }
3353            }
3354            _ => {}
3355        }
3356    }
3357
3358    /// Replaces the top of the interpreter stack with `value`.
3359    ///
3360    /// The caller must only invoke this after a successful opcode that pushed
3361    /// a value onto the stack; the `pop()` is therefore expected to succeed.
3362    /// If it does not (e.g. because of a bug in the caller's success gating)
3363    /// we bail out instead of pushing on top of an unexpected stack, which
3364    /// would silently grow the stack and corrupt the frame.
3365    fn replace_top_of_stack(interpreter: &mut Interpreter, value: U256) {
3366        if interpreter.stack.pop().is_err() {
3367            debug_assert!(false, "env override expected opcode result on stack");
3368            return;
3369        }
3370        let _ = interpreter.stack.push(value);
3371    }
3372
3373    /// Generates or copies arbitrary values for storage slots.
3374    /// Invoked in inspector `step_end` (when the current opcode is not executed), if current opcode
3375    /// to execute is `SLOAD` and storage slot is cold.
3376    /// Ensures that in next step (when `SLOAD` opcode is executed) an arbitrary value is returned:
3377    /// - copies the existing arbitrary storage value (or the new generated one if no value in
3378    ///   cache) from mapped source address to the target address.
3379    /// - generates arbitrary value and saves it in target address storage.
3380    #[cold]
3381    fn arbitrary_storage_end(
3382        &mut self,
3383        interpreter: &mut Interpreter,
3384        ecx: &mut FoundryContextFor<'_, FEN>,
3385    ) {
3386        let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD {
3387            (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address)
3388        } else {
3389            return;
3390        };
3391
3392        if self.is_arbitrary_storage_slot_explicit(target_address, key) {
3393            return;
3394        }
3395
3396        let Some(value) = ecx.sload(target_address, key) else {
3397            return;
3398        };
3399
3400        if (value.is_cold && value.data.is_zero())
3401            || self.should_overwrite_arbitrary_storage(&target_address, key)
3402        {
3403            if self.has_arbitrary_storage(&target_address) {
3404                let arbitrary_value = self
3405                    .cached_arbitrary_storage_value(target_address, key)
3406                    .unwrap_or_else(|| self.rng().random());
3407                self.arbitrary_storage.as_mut().unwrap().save(
3408                    ecx,
3409                    target_address,
3410                    key,
3411                    arbitrary_value,
3412                );
3413            } else if self.is_arbitrary_storage_copy(&target_address) {
3414                let arbitrary_value = self.rng().random();
3415                self.arbitrary_storage.as_mut().unwrap().copy(
3416                    ecx,
3417                    target_address,
3418                    key,
3419                    arbitrary_value,
3420                );
3421            }
3422        }
3423    }
3424
3425    /// Restores parent interpreter state after a synthetic storage-hook callback.
3426    ///
3427    /// Returns whether a failed callback was propagated to the parent frame.
3428    #[inline]
3429    pub fn finish_storage_hook_callback(
3430        &mut self,
3431        interpreter: &mut Interpreter,
3432        ecx: &mut FoundryContextFor<'_, FEN>,
3433    ) -> bool {
3434        let Some(active) = self.active_storage_hook.as_ref() else { return false };
3435        let Some((result, output)) = active.outcome.clone() else { return false };
3436
3437        let active = self.active_storage_hook.take().expect("active storage hook exists");
3438        Self::restore_storage_hook_access(ecx, active.journal_start);
3439        self.restore_storage_hook_inspector_state(active.inspector_state);
3440        let _ = interpreter.stack.pop();
3441        if let Some(item) = active.saved_stack_item {
3442            let result = interpreter.stack.push(item);
3443            debug_assert!(result, "reserved storage-hook stack slot must be available");
3444        }
3445        interpreter.gas = active.saved_gas;
3446        interpreter.return_data.set_buffer(active.saved_return_data);
3447
3448        if result.is_ok() {
3449            false
3450        } else {
3451            interpreter.bytecode.set_action(InterpreterAction::new_return(
3452                InstructionResult::Revert,
3453                output,
3454                interpreter.gas,
3455            ));
3456            true
3457        }
3458    }
3459
3460    fn take_storage_hook_inspector_state(&mut self) -> StorageHookInspectorState {
3461        StorageHookInspectorState {
3462            accesses: std::mem::take(&mut self.accesses),
3463            recording_accesses: std::mem::replace(&mut self.recording_accesses, false),
3464            mapping_slots: self.mapping_slots.take(),
3465            recorded_logs: self.recorded_logs.take(),
3466            mocked_calls: std::mem::take(&mut self.mocked_calls),
3467            mocked_functions: std::mem::take(&mut self.mocked_functions),
3468            expected_revert: self.expected_revert.take(),
3469            assume_no_revert: self.assume_no_revert.take(),
3470            expected_calls: std::mem::take(&mut self.expected_calls),
3471            expected_emits: std::mem::take(&mut self.expected_emits),
3472            expected_creates: std::mem::take(&mut self.expected_creates),
3473        }
3474    }
3475
3476    fn restore_storage_hook_inspector_state(&mut self, state: StorageHookInspectorState) {
3477        self.accesses = state.accesses;
3478        self.recording_accesses = state.recording_accesses;
3479        self.mapping_slots = state.mapping_slots;
3480        self.recorded_logs = state.recorded_logs;
3481        self.mocked_calls = state.mocked_calls;
3482        self.mocked_functions = state.mocked_functions;
3483        self.expected_revert = state.expected_revert;
3484        self.assume_no_revert = state.assume_no_revert;
3485        self.expected_calls = state.expected_calls;
3486        self.expected_emits = state.expected_emits;
3487        self.expected_creates = state.expected_creates;
3488    }
3489
3490    fn restore_storage_hook_access(ecx: &mut FoundryContextFor<'_, FEN>, journal_start: usize) {
3491        let (_, journal) = ecx.db_journal_inner_mut();
3492        let entries =
3493            journal.journal.drain(journal_start.min(journal.journal.len())..).collect_vec();
3494        for entry in entries {
3495            match entry {
3496                JournalEntry::AccountWarmed { address } => {
3497                    journal.state.get_mut(&address).expect("warmed account exists").mark_cold();
3498                }
3499                JournalEntry::StorageWarmed { address, key } => {
3500                    // TODO(@mablr): Preserve the EIP-2200 `original_value` when bumping the REVM
3501                    // family to 42. REVM 41's `mark_cold` resets it for a slot first warmed and
3502                    // modified by the callback.
3503                    journal
3504                        .state
3505                        .get_mut(&address)
3506                        .expect("warmed account exists")
3507                        .storage
3508                        .get_mut(&key)
3509                        .expect("warmed storage slot exists")
3510                        .mark_cold();
3511                }
3512                entry => journal.journal.push(entry),
3513            }
3514        }
3515    }
3516
3517    fn capture_storage_hook(
3518        &mut self,
3519        interpreter: &Interpreter,
3520        ecx: &mut FoundryContextFor<'_, FEN>,
3521    ) {
3522        self.pending_storage_hook = None;
3523        if self.active_storage_hook.is_some() {
3524            return;
3525        }
3526        let account = interpreter.input.target_address;
3527        match interpreter.bytecode.opcode() {
3528            op::SLOAD => {
3529                let slot = try_or_return!(interpreter.stack.peek(0));
3530                let Some(hook) = self.storage_load_hooks.get(&account).copied() else { return };
3531                self.pending_storage_hook = Some(PendingStorageHook::Load { account, slot, hook });
3532            }
3533            op::SSTORE => {
3534                let slot = try_or_return!(interpreter.stack.peek(0));
3535                let (hook, mapping) = if let Some(hook) = self.storage_store_hooks.get(&account) {
3536                    (*hook, None)
3537                } else {
3538                    let Some(provenance) = self
3539                        .storage_hook_mapping_slots
3540                        .get(&account)
3541                        .and_then(|slots| slots.resolve(slot.into()))
3542                    else {
3543                        return;
3544                    };
3545                    let Some(hook) = self
3546                        .mapping_storage_store_hooks
3547                        .get(&account)
3548                        .and_then(|hooks| hooks.get(&provenance.root_slot))
3549                        .copied()
3550                    else {
3551                        return;
3552                    };
3553                    (hook, Some((provenance.root_slot, provenance.keys)))
3554                };
3555                let checkpoint = ecx.journal_mut().checkpoint();
3556                let old_value =
3557                    ecx.sload(account, slot).map(|value| value.data).unwrap_or_default();
3558                ecx.journal_mut().checkpoint_revert(checkpoint);
3559                self.pending_storage_hook =
3560                    Some(PendingStorageHook::Store { account, slot, old_value, mapping, hook });
3561            }
3562            _ => {}
3563        }
3564    }
3565
3566    fn invoke_pending_storage_hook(
3567        &mut self,
3568        interpreter: &mut Interpreter,
3569        ecx: &mut FoundryContextFor<'_, FEN>,
3570    ) {
3571        let Some(pending) = self.pending_storage_hook.take() else { return };
3572        if interpreter
3573            .bytecode
3574            .action
3575            .as_ref()
3576            .and_then(InterpreterAction::instruction_result)
3577            .is_some()
3578        {
3579            return;
3580        }
3581
3582        let (hook, input, saved_stack_item) = match pending {
3583            PendingStorageHook::Load { account, slot, hook } => {
3584                let value = try_or_return!(interpreter.stack.peek(0));
3585                let mut input = Vec::with_capacity(4 + 32 * 3);
3586                input.extend_from_slice(&hook.callback_selector);
3587                input.extend_from_slice(account.into_word().as_slice());
3588                input.extend_from_slice(&slot.to_be_bytes::<32>());
3589                input.extend_from_slice(&value.to_be_bytes::<32>());
3590                (hook, Bytes::from(input), Some(value))
3591            }
3592            PendingStorageHook::Store { account, slot, old_value, mapping, hook } => {
3593                let new_value =
3594                    ecx.sload(account, slot).map(|value| value.data).unwrap_or_default();
3595                let mut input = Vec::with_capacity(4 + 32 * 4);
3596                input.extend_from_slice(&hook.callback_selector);
3597                input.extend_from_slice(account.into_word().as_slice());
3598                input.extend_from_slice(&slot.to_be_bytes::<32>());
3599                if let Some((root, keys)) = mapping {
3600                    input.extend_from_slice(root.as_slice());
3601                    input.extend_from_slice(&U256::from(32 * 6).to_be_bytes::<32>());
3602                    input.extend_from_slice(&old_value.to_be_bytes::<32>());
3603                    input.extend_from_slice(&new_value.to_be_bytes::<32>());
3604                    input.extend_from_slice(&U256::from(keys.len()).to_be_bytes::<32>());
3605                    for key in keys {
3606                        input.extend_from_slice(key.as_slice());
3607                    }
3608                } else {
3609                    input.extend_from_slice(&old_value.to_be_bytes::<32>());
3610                    input.extend_from_slice(&new_value.to_be_bytes::<32>());
3611                }
3612                (hook, Bytes::from(input), None)
3613            }
3614        };
3615
3616        let journal_start = ecx.db_journal_inner_mut().1.journal.len();
3617        let account = match ecx.journal_mut().load_account_with_code(hook.callback_target) {
3618            Ok(account) => account,
3619            Err(err) => {
3620                interpreter.bytecode.set_action(InterpreterAction::new_return(
3621                    InstructionResult::Revert,
3622                    Error::encode(err),
3623                    interpreter.gas,
3624                ));
3625                return;
3626            }
3627        };
3628        let known_bytecode =
3629            (account.info.code_hash, account.info.code.clone().unwrap_or_default());
3630        let saved_gas = interpreter.gas;
3631        let saved_return_data = Bytes::copy_from_slice(interpreter.return_data.buffer());
3632        let gas_limit = interpreter.gas.remaining();
3633        let parent_depth = ecx.journal().depth();
3634        if saved_stack_item.is_some() {
3635            let result = interpreter.stack.pop();
3636            debug_assert!(result.is_ok(), "captured SLOAD result must be on the stack");
3637        }
3638        let inspector_state = self.take_storage_hook_inspector_state();
3639
3640        self.active_storage_hook = Some(ActiveStorageHook {
3641            parent_depth,
3642            callback_target: hook.callback_target,
3643            callback_input: input.clone(),
3644            saved_gas,
3645            saved_return_data,
3646            saved_stack_item,
3647            journal_start,
3648            inspector_state,
3649            outcome: None,
3650        });
3651        interpreter.bytecode.set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
3652            CallInputs {
3653                input: CallInput::Bytes(input),
3654                return_memory_offset: 0..0,
3655                gas_limit,
3656                reservoir: 0,
3657                bytecode_address: hook.callback_target,
3658                known_bytecode,
3659                target_address: hook.callback_target,
3660                caller: CHEATCODE_ADDRESS,
3661                value: CallValue::Transfer(U256::ZERO),
3662                scheme: CallScheme::Call,
3663                is_static: false,
3664                charged_new_account_state_gas: false,
3665            },
3666        ))));
3667    }
3668
3669    /// Records storage slots reads and writes.
3670    #[cold]
3671    fn record_accesses(&mut self, interpreter: &mut Interpreter) {
3672        let access = &mut self.accesses;
3673        match interpreter.bytecode.opcode() {
3674            op::SLOAD => {
3675                let key = try_or_return!(interpreter.stack.peek(0));
3676                access.record_read(interpreter.input.target_address, key);
3677            }
3678            op::SSTORE => {
3679                let key = try_or_return!(interpreter.stack.peek(0));
3680                access.record_write(interpreter.input.target_address, key);
3681            }
3682            _ => {}
3683        }
3684    }
3685
3686    #[cold]
3687    fn record_state_diffs(
3688        &mut self,
3689        interpreter: &mut Interpreter,
3690        ecx: &mut FoundryContextFor<'_, FEN>,
3691    ) {
3692        let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return };
3693        match interpreter.bytecode.opcode() {
3694            op::SELFDESTRUCT => {
3695                // Ensure that we're not selfdestructing a context recording was initiated on
3696                let Some(last) = account_accesses.last_mut() else { return };
3697
3698                // get previous balance, nonce and initialized status of the target account
3699                let target = try_or_return!(interpreter.stack.peek(0));
3700                let target = Address::from_word(B256::from(target));
3701                let (initialized, old_balance, old_nonce) = ecx
3702                    .journal_mut()
3703                    .load_account(target)
3704                    .map(|account| {
3705                        (
3706                            account.data.info.exists(),
3707                            account.data.info.balance,
3708                            account.data.info.nonce,
3709                        )
3710                    })
3711                    .unwrap_or_default();
3712
3713                // load balance of this account
3714                let value = ecx
3715                    .balance(interpreter.input.target_address)
3716                    .map(|b| b.data)
3717                    .unwrap_or(U256::ZERO);
3718
3719                // register access for the target account
3720                last.push(crate::Vm::AccountAccess {
3721                    chainInfo: crate::Vm::ChainInfo {
3722                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
3723                        chainId: U256::from(ecx.cfg().chain_id()),
3724                    },
3725                    accessor: interpreter.input.target_address,
3726                    account: target,
3727                    kind: crate::Vm::AccountAccessKind::SelfDestruct,
3728                    initialized,
3729                    oldBalance: old_balance,
3730                    newBalance: old_balance + value,
3731                    oldNonce: old_nonce,
3732                    newNonce: old_nonce, // nonce doesn't change on selfdestruct
3733                    value,
3734                    data: Bytes::new(),
3735                    reverted: false,
3736                    deployedCode: Bytes::new(),
3737                    storageAccesses: vec![],
3738                    depth: ecx
3739                        .journal()
3740                        .depth()
3741                        .try_into()
3742                        .expect("journaled state depth exceeds u64"),
3743                });
3744            }
3745
3746            op::SLOAD => {
3747                let Some(last) = account_accesses.last_mut() else { return };
3748
3749                let key = try_or_return!(interpreter.stack.peek(0));
3750                let address = interpreter.input.target_address;
3751
3752                // Try to include present value for informational purposes, otherwise assume
3753                // it's not set (zero value). Revert the checkpoint so this read does not warm the
3754                // slot for the actual SLOAD opcode.
3755                let checkpoint = ecx.journal_mut().checkpoint();
3756                let present_value =
3757                    ecx.sload(address, key).map(|previous| previous.data).unwrap_or_default();
3758                ecx.journal_mut().checkpoint_revert(checkpoint);
3759                let access = crate::Vm::StorageAccess {
3760                    account: interpreter.input.target_address,
3761                    slot: key.into(),
3762                    isWrite: false,
3763                    previousValue: present_value.into(),
3764                    newValue: present_value.into(),
3765                    reverted: false,
3766                };
3767                let curr_depth =
3768                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3769                append_storage_access(last, access, curr_depth);
3770            }
3771            op::SSTORE => {
3772                let Some(last) = account_accesses.last_mut() else { return };
3773
3774                let key = try_or_return!(interpreter.stack.peek(0));
3775                let value = try_or_return!(interpreter.stack.peek(1));
3776                let address = interpreter.input.target_address;
3777                // Try to load the account and the slot's previous value, otherwise, assume it's
3778                // not set (zero value). Revert the checkpoint so this read does not warm the slot
3779                // for the actual SSTORE opcode.
3780                let checkpoint = ecx.journal_mut().checkpoint();
3781                let previous_value =
3782                    ecx.sload(address, key).map(|previous| previous.data).unwrap_or_default();
3783                ecx.journal_mut().checkpoint_revert(checkpoint);
3784
3785                let access = crate::Vm::StorageAccess {
3786                    account: address,
3787                    slot: key.into(),
3788                    isWrite: true,
3789                    previousValue: previous_value.into(),
3790                    newValue: value.into(),
3791                    reverted: false,
3792                };
3793                let curr_depth =
3794                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3795                append_storage_access(last, access, curr_depth);
3796            }
3797
3798            // Record account accesses via the EXT family of opcodes
3799            op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => {
3800                let kind = match interpreter.bytecode.opcode() {
3801                    op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy,
3802                    op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize,
3803                    op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash,
3804                    op::BALANCE => crate::Vm::AccountAccessKind::Balance,
3805                    _ => unreachable!(),
3806                };
3807                let address =
3808                    Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0))));
3809                let checkpoint = ecx.journal_mut().checkpoint();
3810                let (initialized, balance, nonce) = ecx
3811                    .journal_mut()
3812                    .load_account(address)
3813                    .map(|acc| (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce))
3814                    .unwrap_or_default();
3815                ecx.journal_mut().checkpoint_revert(checkpoint);
3816                let curr_depth =
3817                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3818                let account_access = crate::Vm::AccountAccess {
3819                    chainInfo: crate::Vm::ChainInfo {
3820                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
3821                        chainId: U256::from(ecx.cfg().chain_id()),
3822                    },
3823                    accessor: interpreter.input.target_address,
3824                    account: address,
3825                    kind,
3826                    initialized,
3827                    oldBalance: balance,
3828                    newBalance: balance,
3829                    oldNonce: nonce,
3830                    newNonce: nonce, // EXT* operations don't change nonce
3831                    value: U256::ZERO,
3832                    data: Bytes::new(),
3833                    reverted: false,
3834                    deployedCode: Bytes::new(),
3835                    storageAccesses: vec![],
3836                    depth: curr_depth,
3837                };
3838                // Record the EXT* call as an account access at the current depth
3839                // (future storage accesses will be recorded in a new "Resume" context)
3840                if let Some(last) = account_accesses.last_mut() {
3841                    last.push(account_access);
3842                } else {
3843                    account_accesses.push(vec![account_access]);
3844                }
3845            }
3846            _ => {}
3847        }
3848    }
3849
3850    /// Checks to see if the current opcode can either mutate directly or expand memory.
3851    ///
3852    /// If the opcode at the current program counter is a match, check if the modified memory lies
3853    /// within the allowed ranges. If not, revert and fail the test.
3854    #[cold]
3855    fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) {
3856        let Some(ranges) = self.allowed_mem_writes.get(&depth) else {
3857            return;
3858        };
3859
3860        // The `mem_opcode_match` macro is used to match the current opcode against a list of
3861        // opcodes that can mutate memory (either directly or expansion via reading). If the
3862        // opcode is a match, the memory offsets that are being written to are checked to be
3863        // within the allowed ranges. If not, the test is failed and the transaction is
3864        // reverted. For all opcodes that can mutate memory aside from MSTORE,
3865        // MSTORE8, and MLOAD, the size and destination offset are on the stack, and
3866        // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the
3867        // size of the memory write is implicit, so these cases are hard-coded.
3868        macro_rules! mem_opcode_match {
3869            ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => {
3870                match interpreter.bytecode.opcode() {
3871                    ////////////////////////////////////////////////////////////////
3872                    //    OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING     //
3873                    ////////////////////////////////////////////////////////////////
3874
3875                    op::MSTORE => {
3876                        // The offset of the mstore operation is at the top of the stack.
3877                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3878
3879                        // If none of the allowed ranges contain [offset, offset + 32), memory has been
3880                        // unexpectedly mutated.
3881                        if !ranges.iter().any(|range| {
3882                            range.contains(&offset) && range.contains(&(offset + 31))
3883                        }) {
3884                            // SPECIAL CASE: When the compiler attempts to store the selector for
3885                            // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory
3886                            // pointer, which could have been updated to the exclusive upper bound during
3887                            // execution.
3888                            let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>();
3889                            if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
3890                                return
3891                            }
3892
3893                            disallowed_mem_write(offset, 32, interpreter, ranges);
3894                            return
3895                        }
3896                    }
3897                    op::MSTORE8 => {
3898                        // The offset of the mstore8 operation is at the top of the stack.
3899                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3900
3901                        // If none of the allowed ranges contain the offset, memory has been
3902                        // unexpectedly mutated.
3903                        if !ranges.iter().any(|range| range.contains(&offset)) {
3904                            disallowed_mem_write(offset, 1, interpreter, ranges);
3905                            return
3906                        }
3907                    }
3908
3909                    ////////////////////////////////////////////////////////////////
3910                    //        OPERATIONS THAT CAN EXPAND MEMORY BY READING        //
3911                    ////////////////////////////////////////////////////////////////
3912
3913                    op::MLOAD => {
3914                        // The offset of the mload operation is at the top of the stack
3915                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3916
3917                        // If the offset being loaded is >= than the memory size, the
3918                        // memory is being expanded. If none of the allowed ranges contain
3919                        // [offset, offset + 32), memory has been unexpectedly mutated.
3920                        if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| {
3921                            range.contains(&offset) && range.contains(&(offset + 31))
3922                        }) {
3923                            disallowed_mem_write(offset, 32, interpreter, ranges);
3924                            return
3925                        }
3926                    }
3927
3928                    ////////////////////////////////////////////////////////////////
3929                    //          OPERATIONS WITH OFFSET AND SIZE ON STACK          //
3930                    ////////////////////////////////////////////////////////////////
3931
3932                    op::CALL => {
3933                        // The destination offset of the operation is the fifth element on the stack.
3934                        let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::<u64>();
3935
3936                        // The size of the data that will be copied is the sixth element on the stack.
3937                        let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::<u64>();
3938
3939                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
3940                        // memory outside of the expected ranges has been touched. If the opcode
3941                        // only reads from memory, this is okay as long as the memory is not expanded.
3942                        let fail_cond = !ranges.iter().any(|range| {
3943                            range.contains(&dest_offset) &&
3944                                range.contains(&(dest_offset + size.saturating_sub(1)))
3945                        });
3946
3947                        // If the failure condition is met, set the output buffer to a revert string
3948                        // that gives information about the allowed ranges and revert.
3949                        if fail_cond {
3950                            // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed.
3951                            // It allocated calldata at the current free memory pointer, and will attempt to read
3952                            // from this memory region to perform the call.
3953                            let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into());
3954                            if to == CHEATCODE_ADDRESS {
3955                                let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::<usize>();
3956                                let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::<usize>();
3957                                // CALL has not expanded input memory yet.
3958                                if args_size >= SELECTOR_LEN
3959                                    && args_offset.saturating_add(args_size) <= interpreter.memory.size()
3960                                {
3961                                    let memory_word = interpreter.memory.slice_len(args_offset, args_size);
3962                                    if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
3963                                        return
3964                                    }
3965                                }
3966                            }
3967
3968                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
3969                            return
3970                        }
3971                    }
3972
3973                    $(op::$opcode => {
3974                        // The destination offset of the operation.
3975                        let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::<u64>();
3976
3977                        // The size of the data that will be copied.
3978                        let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::<u64>();
3979
3980                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
3981                        // memory outside of the expected ranges has been touched. If the opcode
3982                        // only reads from memory, this is okay as long as the memory is not expanded.
3983                        let fail_cond = !ranges.iter().any(|range| {
3984                                range.contains(&dest_offset) &&
3985                                    range.contains(&(dest_offset + size.saturating_sub(1)))
3986                            }) && ($writes ||
3987                                [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| {
3988                                    offset >= interpreter.memory.size() as u64
3989                                })
3990                            );
3991
3992                        // If the failure condition is met, set the output buffer to a revert string
3993                        // that gives information about the allowed ranges and revert.
3994                        if fail_cond {
3995                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
3996                            return
3997                        }
3998                    })*
3999
4000                    _ => {}
4001                }
4002            }
4003        }
4004
4005        // Check if the current opcode can write to memory, and if so, check if the memory
4006        // being written to is registered as safe to modify.
4007        mem_opcode_match!(
4008            (CALLDATACOPY, 0, 2, true),
4009            (CODECOPY, 0, 2, true),
4010            (RETURNDATACOPY, 0, 2, true),
4011            (EXTCODECOPY, 1, 3, true),
4012            (CALLCODE, 5, 6, true),
4013            (STATICCALL, 4, 5, true),
4014            (DELEGATECALL, 4, 5, true),
4015            (KECCAK256, 0, 1, false),
4016            (LOG0, 0, 1, false),
4017            (LOG1, 0, 1, false),
4018            (LOG2, 0, 1, false),
4019            (LOG3, 0, 1, false),
4020            (LOG4, 0, 1, false),
4021            (CREATE, 1, 2, false),
4022            (CREATE2, 1, 2, false),
4023            (RETURN, 0, 1, false),
4024            (REVERT, 0, 1, false),
4025        );
4026    }
4027
4028    #[cold]
4029    fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
4030        match interpreter.bytecode.opcode() {
4031            op::CREATE2 => self.dynamic_gas_limit = true,
4032            op::CALL => {
4033                // If first element of the stack is close to current remaining gas then assume
4034                // dynamic gas limit.
4035                self.dynamic_gas_limit =
4036                    try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
4037            }
4038            _ => self.dynamic_gas_limit = false,
4039        }
4040    }
4041}
4042
4043/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,
4044/// and sets the return range to the revert string's location in memory.
4045///
4046/// This will set the interpreter's next action to a return with the revert string as the output.
4047/// And trigger a revert.
4048fn disallowed_mem_write(
4049    dest_offset: u64,
4050    size: u64,
4051    interpreter: &mut Interpreter,
4052    ranges: &[Range<u64>],
4053) {
4054    let revert_string = format!(
4055        "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}",
4056        dest_offset,
4057        size,
4058        ranges.iter().map(|r| format!("[0x{:02X}, 0x{:02X})", r.start, r.end)).join(" U ")
4059    );
4060
4061    interpreter.bytecode.set_action(InterpreterAction::new_return(
4062        InstructionResult::Revert,
4063        Bytes::from(revert_string.into_bytes()),
4064        interpreter.gas,
4065    ));
4066}
4067
4068/// Returns true if the kind of account access is a call.
4069const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
4070    matches!(
4071        kind,
4072        crate::Vm::AccountAccessKind::Call
4073            | crate::Vm::AccountAccessKind::StaticCall
4074            | crate::Vm::AccountAccessKind::CallCode
4075            | crate::Vm::AccountAccessKind::DelegateCall
4076    )
4077}
4078
4079/// Records a log into the recorded logs vector, if it exists.
4080fn record_logs(recorded_logs: &mut Option<Vec<Vm::Log>>, log: &Log) {
4081    if let Some(storage_recorded_logs) = recorded_logs {
4082        storage_recorded_logs.push(Vm::Log {
4083            topics: log.data.topics().to_vec(),
4084            data: log.data.data.clone(),
4085            emitter: log.address,
4086        });
4087    }
4088}
4089
4090/// Appends an AccountAccess that resumes the recording of the current context.
4091fn append_storage_access(
4092    last: &mut Vec<AccountAccess>,
4093    storage_access: crate::Vm::StorageAccess,
4094    storage_depth: u64,
4095) {
4096    // Assert that there's an existing record for the current context.
4097    if !last.is_empty() && last.first().unwrap().depth < storage_depth {
4098        // Three cases to consider:
4099        // 1. If there hasn't been a context switch since the start of this context, then add the
4100        //    storage access to the current context record.
4101        // 2. If there's an existing Resume record, then add the storage access to it.
4102        // 3. Otherwise, create a new Resume record based on the current context.
4103        if last.len() == 1 {
4104            last.first_mut().unwrap().storageAccesses.push(storage_access);
4105        } else {
4106            let last_record = last.last_mut().unwrap();
4107            if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 {
4108                last_record.storageAccesses.push(storage_access);
4109            } else {
4110                let entry = last.first().unwrap();
4111                let resume_record = crate::Vm::AccountAccess {
4112                    chainInfo: crate::Vm::ChainInfo {
4113                        forkId: entry.chainInfo.forkId,
4114                        chainId: entry.chainInfo.chainId,
4115                    },
4116                    accessor: entry.accessor,
4117                    account: entry.account,
4118                    kind: crate::Vm::AccountAccessKind::Resume,
4119                    initialized: entry.initialized,
4120                    storageAccesses: vec![storage_access],
4121                    reverted: entry.reverted,
4122                    // The remaining fields are defaults
4123                    oldBalance: U256::ZERO,
4124                    newBalance: U256::ZERO,
4125                    oldNonce: 0,
4126                    newNonce: 0,
4127                    value: U256::ZERO,
4128                    data: Bytes::new(),
4129                    deployedCode: Bytes::new(),
4130                    depth: entry.depth,
4131                };
4132                last.push(resume_record);
4133            }
4134        }
4135    }
4136}
4137
4138/// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor.
4139const fn cheatcode_of<T: spec::CheatcodeDef>(_: &T) -> &'static spec::Cheatcode<'static> {
4140    T::CHEATCODE
4141}
4142
4143fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str {
4144    cheat.func.signature.split('(').next().unwrap()
4145}
4146
4147const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str {
4148    cheat.func.id
4149}
4150
4151const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str {
4152    cheat.func.signature
4153}
4154
4155/// Dispatches the cheatcode call to the appropriate function.
4156fn apply_dispatch<FEN: FoundryEvmNetwork>(
4157    calls: &Vm::VmCalls,
4158    ccx: &mut CheatsCtxt<'_, '_, FEN>,
4159    executor: &mut dyn CheatcodesExecutor<FEN>,
4160) -> Result {
4161    // Extract metadata for logging/deprecation via CheatcodeDef.
4162    macro_rules! get_cheatcode {
4163        ($($variant:ident),*) => {
4164            match calls {
4165                $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)*
4166            }
4167        };
4168    }
4169    let cheat = vm_calls!(get_cheatcode);
4170
4171    let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered();
4172    trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying");
4173
4174    if let spec::Status::Deprecated(replacement) = cheat.status {
4175        ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement);
4176    }
4177
4178    // Monomorphized dispatch: calls apply_full directly, no trait objects.
4179    macro_rules! dispatch {
4180        ($($variant:ident),*) => {
4181            match calls {
4182                $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)*
4183            }
4184        };
4185    }
4186    let mut result = if ccx.state.config.blocked_cheatcodes.contains(&cheat.func.selector_bytes) {
4187        Err(fmt_err!("disabled during restricted execution"))
4188    } else {
4189        vm_calls!(dispatch)
4190    };
4191
4192    // Format the error message to include the cheatcode name.
4193    if let Err(e) = &mut result
4194        && e.is_str()
4195    {
4196        let name = cheatcode_name(cheat);
4197        // Skip showing the cheatcode name for:
4198        // - assertions: too verbose, and can already be inferred from the error message
4199        // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl`
4200        if !name.contains("assert") && name != "rpcUrl" {
4201            *e = fmt_err!("vm.{name}: {e}");
4202        }
4203    }
4204
4205    trace!(
4206        target: "cheatcodes",
4207        return = %match &result {
4208            Ok(b) => hex::encode(b),
4209            Err(e) => e.to_string(),
4210        }
4211    );
4212
4213    result
4214}
4215
4216/// Helper function to check if frame execution will exit.
4217const fn will_exit(action: &InterpreterAction) -> bool {
4218    match action {
4219        InterpreterAction::Return(result) => {
4220            result.result.is_ok_or_revert() || result.result.is_halt()
4221        }
4222        _ => false,
4223    }
4224}
4225
4226#[cfg(test)]
4227mod tests {
4228    use super::*;
4229
4230    fn cheats(flag: bool, broadcast: Option<Broadcast>) -> Cheatcodes {
4231        let config = CheatsConfig { batch_rewrite_creates: flag, ..Default::default() };
4232        let mut cheats = Cheatcodes::new(Arc::new(config));
4233        cheats.broadcast = broadcast;
4234        cheats
4235    }
4236
4237    fn create_inputs() -> CreateInputs {
4238        CreateInputs::new(Address::ZERO, CreateScheme::Create, U256::ZERO, Bytes::new(), 100_000, 0)
4239    }
4240
4241    fn broadcast_at(depth: usize) -> Broadcast {
4242        Broadcast { depth, ..Default::default() }
4243    }
4244
4245    #[test]
4246    fn flag_off_with_broadcast_returns_false() {
4247        let mut cheats = cheats(false, Some(broadcast_at(1)));
4248        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
4249    }
4250
4251    #[test]
4252    fn flag_on_without_broadcast_returns_false() {
4253        let mut cheats = cheats(true, None);
4254        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
4255    }
4256
4257    #[test]
4258    fn flag_on_with_broadcast_depth_mismatch_returns_false() {
4259        let mut cheats = cheats(true, Some(broadcast_at(2)));
4260        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
4261    }
4262
4263    #[test]
4264    fn flag_on_with_broadcast_depth_match_returns_true() {
4265        let mut cheats = cheats(true, Some(broadcast_at(1)));
4266        assert!(cheats.should_use_create2_factory(1, &create_inputs()));
4267    }
4268
4269    #[test]
4270    fn default_cheatcodes_have_no_opcode_hooks() {
4271        let cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4272        assert!(!cheats.has_step_hooks());
4273        assert!(!cheats.has_step_end_hooks());
4274        assert!(!cheats.has_log_hooks());
4275    }
4276
4277    #[test]
4278    fn active_cheatcode_state_enables_opcode_hooks() {
4279        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4280
4281        cheats.recording_accesses = true;
4282        assert!(cheats.has_step_hooks());
4283        assert!(!cheats.has_step_end_hooks());
4284        assert!(cheats.has_recording_accesses_only_step_hook());
4285
4286        cheats.recording_accesses = false;
4287        cheats.gas_metering.touched = true;
4288        assert!(!cheats.has_step_hooks());
4289        assert!(cheats.has_step_end_hooks());
4290        assert!(!cheats.has_recording_accesses_only_step_hook());
4291
4292        cheats.gas_metering.touched = false;
4293        cheats.register_storage_load_hook(Address::ZERO, Address::ZERO, [0; 4]);
4294        assert!(cheats.has_step_hooks());
4295        assert!(cheats.has_step_end_hooks());
4296        assert!(!cheats.has_recording_accesses_only_step_hook());
4297    }
4298
4299    #[test]
4300    fn mixed_step_hooks_disable_record_access_fast_path() {
4301        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4302        cheats.recording_accesses = true;
4303
4304        cheats.gas_metering.reset = true;
4305        assert!(!cheats.has_recording_accesses_only_step_hook());
4306
4307        cheats.gas_metering.reset = false;
4308        cheats.env_overrides.insert(None, EnvOverrides { basefee: Some(1), ..Default::default() });
4309        assert!(!cheats.has_recording_accesses_only_step_hook());
4310    }
4311
4312    #[test]
4313    fn inactive_env_override_entries_do_not_enable_opcode_hooks() {
4314        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4315        cheats.env_overrides.insert(None, EnvOverrides::default());
4316
4317        assert!(!cheats.has_step_hooks());
4318        assert!(!cheats.has_step_end_hooks());
4319
4320        cheats.env_overrides.get_mut(&None).unwrap().basefee = Some(1);
4321        assert!(cheats.has_step_hooks());
4322        assert!(cheats.has_step_end_hooks());
4323    }
4324
4325    #[test]
4326    fn active_log_state_enables_log_hooks() {
4327        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4328
4329        cheats.recorded_logs = Some(Default::default());
4330        assert!(cheats.has_log_hooks());
4331
4332        cheats.recorded_logs = None;
4333        cheats.expected_emits.push_back((
4334            expect::ExpectedEmit {
4335                depth: 0,
4336                log: None,
4337                checks: [false; 5],
4338                address: None,
4339                anonymous: false,
4340                found: false,
4341                count: 1,
4342                mismatch_error: None,
4343            },
4344            Default::default(),
4345        ));
4346        assert!(cheats.has_log_hooks());
4347    }
4348
4349    #[test]
4350    fn frame_gas_reports_settled_components() {
4351        for mut gas in [Gas::new(100_000), Gas::new_with_regular_gas_and_reservoir(100_000, 50_000)]
4352        {
4353            assert!(gas.record_regular_cost(1_000));
4354            assert!(gas.record_state_cost(20_000));
4355
4356            let mut result = InterpreterResult::new(InstructionResult::Stop, Bytes::new(), gas);
4357            let reported = frame_gas(&result);
4358            assert_eq!(reported.gasTotalUsed, 1_000);
4359            assert_eq!(reported.gasStateUsed, 20_000);
4360
4361            result.result = InstructionResult::Revert;
4362            assert_eq!(frame_gas(&result).gasStateUsed, 0);
4363        }
4364
4365        let mut gas = Gas::new(100_000);
4366        gas.refill_reservoir(20_000);
4367        let result = InterpreterResult::new(InstructionResult::Stop, Bytes::new(), gas);
4368        assert_eq!(frame_gas(&result).gasStateUsed, -20_000);
4369
4370        let mut gas = Gas::new(100_000);
4371        assert!(gas.record_state_cost(20_000));
4372        gas.spend_all();
4373        let result = InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), gas);
4374        let reported = frame_gas(&result);
4375        assert_eq!(reported.gasTotalUsed, 100_000);
4376        assert_eq!(reported.gasStateUsed, 0);
4377    }
4378
4379    #[test]
4380    fn arbitrary_storage_cache_value_routes_copied_targets_to_source() {
4381        let mut storage = ArbitraryStorage::default();
4382        let source = Address::repeat_byte(0x11);
4383        let copied = Address::repeat_byte(0x22);
4384        let slot = U256::from(7);
4385
4386        storage.mark_arbitrary(&source, false);
4387        storage.mark_copy(&source, &copied);
4388        storage.cache_value(copied, slot, U256::ZERO);
4389
4390        assert_eq!(storage.cached_value(source, slot), Some(U256::ZERO));
4391    }
4392}