Skip to main content

foundry_cheatcodes/
inspector.rs

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