Skip to main content

foundry_evm/inspectors/
stack.rs

1use super::{
2    Cheatcodes, CheatsConfig, ChiselState, CmpOperands, CustomPrintTracer, EdgeCovConfig,
3    EdgeCovInspector, EdgeCoverage, Fuzzer, LineCoverageCollector, LogCollector, RevertDiagnostic,
4    ScriptExecutionInspector, TempoLabels, TracingInspector,
5};
6use alloy_primitives::{
7    Address, B256, Bytes, Log, TxKind, U256, keccak256,
8    map::{AddressHashMap, AddressHashSet, AddressMap},
9};
10
11use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosureFor, Wallets};
12use foundry_common::{compile::Analysis, sh_warn};
13use foundry_config::FuzzCorpusConfig;
14use foundry_evm_core::{
15    FoundryBlock, FoundryTransaction, InspectorExt,
16    backend::{ContextUpdateFor, DatabaseError, DatabaseExt, JournaledState},
17    constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH,
18    env::FoundryContextExt,
19    evm::{
20        BlockEnvFor, ChainFor, EthEvmNetwork, EvmEnvFor, EvmFactoryFor, FoundryContextFor,
21        FoundryEvmFactory, FoundryEvmNetwork, TxEnvFor, get_create2_factory_call_inputs,
22        merge_child_state, prepare_child_state, with_inherited_evm,
23    },
24    precompiles::P256_VERIFY,
25};
26use foundry_evm_coverage::HitMaps;
27use foundry_evm_networks::{NetworkConfigs, arbitrum};
28use foundry_evm_traces::{SparsedTraceArena, TraceRequirements};
29use revm::{
30    Inspector, JournalEntry,
31    context::{
32        Block, Cfg, ContextTr, JournalTr, Transaction, TransactionType,
33        result::{EVMError, ExecutionResult, Output},
34    },
35    context_interface::{CreateScheme, journaled_state::JournalCheckpoint},
36    handler::FrameResult,
37    interpreter::{
38        CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
39        InstructionResult, Interpreter, InterpreterResult,
40        bytecode::opcode as op,
41        interpreter_types::{InputsTr, Jumps},
42        return_ok,
43    },
44    primitives::KECCAK_EMPTY,
45    state::Account,
46};
47use std::{
48    ops::{Deref, DerefMut},
49    sync::Arc,
50};
51
52use crate::executors::{EarlyExit, EvmExecutionCancellation, calculate_initial_gas};
53
54#[cfg(feature = "monad")]
55use foundry_evm_core::evm::refresh_chain_journal;
56
57#[derive(Clone, Debug)]
58#[must_use = "builders do nothing unless you call `build` on them"]
59pub struct InspectorStackBuilder<BLOCK: Clone> {
60    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities.
61    pub analysis: Option<Analysis>,
62    /// The block environment.
63    ///
64    /// Used in the cheatcode handler to overwrite the block environment separately from the
65    /// execution block environment.
66    pub block: Option<BLOCK>,
67    /// The gas price.
68    ///
69    /// Used in the cheatcode handler to overwrite the gas price separately from the gas price
70    /// in the execution environment.
71    pub gas_price: Option<u128>,
72    /// The cheatcodes config.
73    pub cheatcodes: Option<Arc<CheatsConfig>>,
74    /// The fuzzer inspector and its state, if it exists.
75    pub fuzzer: Option<Fuzzer>,
76    /// Whether to enable tracing and revert diagnostics.
77    pub trace_requirements: TraceRequirements,
78    /// Whether logs should be collected.
79    /// - None for no log collection.
80    /// - Some(true) for realtime console.log-ing.
81    /// - Some(false) for log collection.
82    pub logs: Option<bool>,
83    /// Whether line coverage info should be collected.
84    pub line_coverage: Option<bool>,
85    /// Whether to print all opcode traces into the console. Useful for debugging the EVM.
86    pub print: Option<bool>,
87    /// The chisel state inspector.
88    pub chisel_state: Option<usize>,
89    /// Whether to enable call isolation.
90    /// In isolation mode all top-level calls are executed as a separate transaction in a separate
91    /// EVM context, enabling more precise gas accounting and transaction state changes.
92    pub enable_isolation: bool,
93    /// Configuration retained for Celo precompile support.
94    // TODO(celo-execution-owner): Replace this residual with concrete Celo precompile
95    // configuration. This is independent of the Monad lifecycle migration.
96    pub networks: NetworkConfigs,
97    /// Concrete Tempo label inspector selected by the Tempo executor builder.
98    tempo_labels: Option<Box<TempoLabels>>,
99    /// Explicitly resolved additional cheatcode addresses.
100    pub extra_cheatcode_addresses: &'static [Address],
101    /// The wallets to set in the cheatcodes context.
102    pub wallets: Option<Wallets>,
103    /// The CREATE2 deployer address.
104    pub create2_deployer: Address,
105}
106
107impl<BLOCK: Clone> Default for InspectorStackBuilder<BLOCK> {
108    fn default() -> Self {
109        Self {
110            analysis: None,
111            block: None,
112            gas_price: None,
113            cheatcodes: None,
114            fuzzer: None,
115            trace_requirements: TraceRequirements::none(),
116            logs: None,
117            line_coverage: None,
118            print: None,
119            chisel_state: None,
120            enable_isolation: false,
121            networks: NetworkConfigs::default(),
122            tempo_labels: None,
123            extra_cheatcode_addresses: &[],
124            wallets: None,
125            create2_deployer: Default::default(),
126        }
127    }
128}
129
130impl<BLOCK: Clone> InspectorStackBuilder<BLOCK> {
131    /// Create a new inspector stack builder.
132    #[inline]
133    pub fn new() -> Self {
134        Self::default()
135    }
136
137    /// Set the solar compiler instance that grants syntactic and semantic analysis capabilities
138    #[inline]
139    pub fn set_analysis(mut self, analysis: Analysis) -> Self {
140        self.analysis = Some(analysis);
141        self
142    }
143
144    /// Set the block environment.
145    #[inline]
146    pub fn block(mut self, block: BLOCK) -> Self {
147        self.block = Some(block);
148        self
149    }
150
151    /// Set the gas price.
152    #[inline]
153    pub const fn gas_price(mut self, gas_price: u128) -> Self {
154        self.gas_price = Some(gas_price);
155        self
156    }
157
158    /// Enable cheatcodes with the given config.
159    #[inline]
160    pub fn cheatcodes(mut self, config: Arc<CheatsConfig>) -> Self {
161        self.cheatcodes = Some(config);
162        self
163    }
164
165    /// Set the wallets.
166    #[inline]
167    pub fn wallets(mut self, wallets: Wallets) -> Self {
168        self.wallets = Some(wallets);
169        self
170    }
171
172    /// Set the fuzzer inspector.
173    #[inline]
174    pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self {
175        self.fuzzer = Some(fuzzer);
176        self
177    }
178
179    /// Set the Chisel inspector.
180    #[inline]
181    pub const fn chisel_state(mut self, final_pc: usize) -> Self {
182        self.chisel_state = Some(final_pc);
183        self
184    }
185
186    /// Set the log collector, and whether to print the logs directly to stdout.
187    #[inline]
188    pub const fn logs(mut self, live_logs: bool) -> Self {
189        self.logs = Some(live_logs);
190        self
191    }
192
193    /// Set whether to collect line coverage information.
194    #[inline]
195    pub const fn line_coverage(mut self, yes: bool) -> Self {
196        self.line_coverage = Some(yes);
197        self
198    }
199
200    /// Set whether to enable the trace printer.
201    #[inline]
202    pub const fn print(mut self, yes: bool) -> Self {
203        self.print = Some(yes);
204        self
205    }
206
207    /// Set trace data requirements.
208    #[inline]
209    pub const fn trace_requirements(mut self, requirements: TraceRequirements) -> Self {
210        self.trace_requirements = self.trace_requirements.merge(requirements);
211        self
212    }
213
214    /// Set whether to enable the call isolation.
215    /// For description of call isolation, see [`InspectorStack::enable_isolation`].
216    #[inline]
217    pub const fn enable_isolation(mut self, yes: bool) -> Self {
218        self.enable_isolation = yes;
219        self
220    }
221
222    /// Sets networks when building an inspector stack directly.
223    ///
224    /// [`ExecutorBuilder::build`](crate::executors::ExecutorBuilder::build) overrides this with its
225    /// explicit network configuration so the executor, backend, and inspector remain in sync.
226    #[inline]
227    pub const fn networks(mut self, networks: NetworkConfigs) -> Self {
228        self.networks = networks;
229        self
230    }
231
232    /// Installs the Tempo label inspector.
233    #[inline]
234    pub(crate) fn tempo_labels(mut self, inspector: TempoLabels) -> Self {
235        self.tempo_labels = Some(Box::new(inspector));
236        self
237    }
238
239    /// Sets explicitly resolved additional cheatcode addresses.
240    #[inline]
241    pub const fn extra_cheatcode_addresses(mut self, addresses: &'static [Address]) -> Self {
242        self.extra_cheatcode_addresses = addresses;
243        self
244    }
245
246    #[inline]
247    pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self {
248        self.create2_deployer = create2_deployer;
249        self
250    }
251
252    /// Builds the stack of inspectors to use when transacting/committing on the EVM.
253    pub fn build<FEN: FoundryEvmNetwork<EvmFactory: FoundryEvmFactory<BlockEnv = BLOCK>>>(
254        self,
255    ) -> InspectorStack<FEN> {
256        let Self {
257            analysis,
258            block,
259            gas_price,
260            cheatcodes,
261            fuzzer,
262            trace_requirements,
263            logs,
264            line_coverage,
265            print,
266            chisel_state,
267            enable_isolation,
268            networks,
269            tempo_labels,
270            extra_cheatcode_addresses,
271            wallets,
272            create2_deployer,
273        } = self;
274        let mut stack = InspectorStack::new();
275        // inspectors
276        if let Some(config) = cheatcodes {
277            let mut cheatcodes = Cheatcodes::new(config);
278            cheatcodes.set_extra_cheatcode_addresses(extra_cheatcode_addresses);
279            // Set analysis capabilities if they are provided
280            if let Some(analysis) = analysis {
281                stack.set_analysis(analysis.clone());
282                cheatcodes.set_analysis(CheatcodeAnalysis::new(analysis));
283            }
284            // Set wallets if they are provided
285            if let Some(wallets) = wallets {
286                cheatcodes.set_wallets(wallets);
287            }
288            stack.set_cheatcodes(cheatcodes);
289        }
290
291        if let Some(fuzzer) = fuzzer {
292            stack.set_fuzzer(fuzzer);
293        }
294        if let Some(chisel_state) = chisel_state {
295            stack.set_chisel(chisel_state);
296        }
297        stack.collect_line_coverage(line_coverage.unwrap_or(false));
298        stack.collect_logs(logs);
299        stack.print(print.unwrap_or(false));
300        stack.tracing_requirements(trace_requirements);
301
302        stack.enable_isolation(enable_isolation);
303        stack.networks(networks);
304        stack.inner.tempo_labels = tempo_labels;
305        stack.set_extra_cheatcode_addresses(extra_cheatcode_addresses);
306        stack.set_create2_deployer(create2_deployer);
307
308        // environment, must come after all of the inspectors
309        if let Some(block) = block {
310            stack.set_block(block);
311        }
312        if let Some(gas_price) = gas_price {
313            stack.set_gas_price(gas_price);
314        }
315
316        stack
317    }
318}
319
320/// Helper macro to call the same method on multiple inspectors without resorting to dynamic
321/// dispatch.
322#[macro_export]
323macro_rules! call_inspectors {
324    ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {
325        $(
326            if let Some($id) = $inspector {
327                $crate::utils::cold_path();
328                $body;
329            }
330        )+
331    };
332    (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{
333        $(
334            if let Some($id) = $inspector {
335                $crate::utils::cold_path();
336                if let Some(result) = $body {
337                    return result;
338                }
339            }
340        )+
341    }};
342}
343
344/// The collected results of [`InspectorStack`].
345pub struct InspectorData<FEN: FoundryEvmNetwork> {
346    pub logs: Vec<Log>,
347    pub labels: AddressHashMap<String>,
348    pub traces: Option<SparsedTraceArena>,
349    pub line_coverage: Option<HitMaps>,
350    pub edge_coverage: Option<EdgeCoverage>,
351    pub evm_cmp_values: Option<Vec<CmpOperands>>,
352    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
353    pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
354    pub reverter: Option<Address>,
355}
356
357/// Contains data about the state of outer/main EVM which created and invoked the inner EVM context.
358/// Used to adjust EVM state while in inner context.
359///
360/// We need this to avoid breaking changes due to EVM behavior differences in isolated vs
361/// non-isolated mode. For descriptions and workarounds for those changes see: <https://github.com/foundry-rs/foundry/pull/7186#issuecomment-1959102195>
362#[derive(Debug, Clone)]
363pub struct InnerContextData {
364    /// Origin of the transaction in the outer EVM context.
365    original_origin: Address,
366    /// Accounts that were created locally before entering the nested EVM context.
367    locally_created_accounts: AddressHashSet,
368    /// Depth of the isolated root frame in the surrounding trace.
369    root_depth: usize,
370}
371
372/// Gas accounting carried across an isolated frame's synthetic transaction boundary.
373struct IsolatedGas {
374    /// Regular execution gas available to the isolated frame.
375    regular_limit: u64,
376    /// State gas reservoir available to the isolated frame.
377    reservoir: u64,
378    /// State gas already charged by the outer opcode.
379    precharged_state: Option<u64>,
380}
381
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
383enum OpcodeStepDispatch {
384    #[default]
385    None,
386    FuzzerOnly,
387    General,
388}
389
390/// An inspector that calls multiple inspectors in sequence.
391///
392/// If a call to an inspector returns a value (indicating a stop or revert) the remaining inspectors
393/// are not called.
394///
395/// Stack is divided into [Cheatcodes] and `InspectorStackInner`. This is done to allow assembling
396/// `InspectorStackRefMut` inside [Cheatcodes] to allow usage of it as [revm::Inspector]. This gives
397/// us ability to create and execute separate EVM frames from inside cheatcodes while still having
398/// access to entire stack of inspectors and correctly handling traces, logs, debugging info
399/// collection, etc.
400#[derive(Clone, Debug)]
401pub struct InspectorStack<FEN: FoundryEvmNetwork = EthEvmNetwork> {
402    #[allow(clippy::type_complexity)]
403    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
404    pub inner: InspectorStackInner,
405}
406
407#[cfg(test)]
408#[derive(Clone, Debug)]
409struct EarlyExitTestGate {
410    entered: std::sync::mpsc::Sender<()>,
411    release: Arc<std::sync::Mutex<std::sync::mpsc::Receiver<()>>>,
412    target_pc: usize,
413    notified: Arc<std::sync::atomic::AtomicBool>,
414}
415
416#[derive(Clone, Copy, Debug)]
417struct PendingCreate2Redirect {
418    depth: usize,
419    charged_create_state_gas: bool,
420}
421
422#[derive(Clone, Copy, Debug)]
423struct PendingCallTrace {
424    trace_idx: usize,
425    executed_address: Option<Address>,
426}
427
428#[derive(Clone, Copy, Debug)]
429struct IsolatedFrameCheckpoint {
430    checkpoint: JournalCheckpoint,
431    restore_len: usize,
432    return_depth: usize,
433    is_create: bool,
434    failed: bool,
435}
436
437/// All used inspectors besides [Cheatcodes].
438///
439/// See [`InspectorStack`].
440#[derive(Default, Clone, Debug)]
441pub struct InspectorStackInner {
442    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities.
443    pub analysis: Option<Analysis>,
444
445    // Inspectors.
446    // These are boxed to reduce the size of the struct and slightly improve performance of the
447    // `if let Some` checks.
448    pub chisel_state: Option<Box<ChiselState>>,
449    pub edge_coverage: Option<Box<EdgeCovInspector>>,
450    pub fuzzer: Option<Box<Fuzzer>>,
451    pub line_coverage: Option<Box<LineCoverageCollector>>,
452    pub log_collector: Option<Box<LogCollector>>,
453    pub printer: Option<Box<CustomPrintTracer>>,
454    pub revert_diag: Option<Box<RevertDiagnostic>>,
455    pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
456    pub tempo_labels: Option<Box<TempoLabels>>,
457    pub tracer: Option<Box<TracingInspector>>,
458
459    // FoundryInspectorExt and other internal data.
460    /// Whether to collect sancov edge coverage from instrumented native crates.
461    pub sancov_edges: bool,
462    /// Whether to capture sancov trace-cmp operands for dictionary injection.
463    pub sancov_trace_cmp: bool,
464    pub enable_isolation: bool,
465    pub networks: NetworkConfigs,
466    /// Additional addresses installed and recognized as cheatcode contracts.
467    pub extra_cheatcode_addresses: &'static [Address],
468    pub create2_deployer: Address,
469    /// Flag marking if we are in the inner EVM context.
470    pub in_inner_context: bool,
471    pub inner_context_data: Option<InnerContextData>,
472    /// Accounts that should retain the per-transaction creation marker in the current context.
473    pub locally_created_accounts: AddressHashSet,
474    pub top_frame_journal: AddressMap<Account>,
475    /// Whether the top-level frame failed before inspector result rewriting.
476    top_level_frame_failed_before_rewrite: bool,
477    /// Fee policy to restore when an isolated call explicitly executes another transaction.
478    outer_disable_fee_charge: Option<bool>,
479    /// Whether the root call of the active isolated transaction executed as a precompile.
480    isolated_call_was_precompile: Option<bool>,
481    /// Synthetic CREATE depth corresponding to an outer top-level deployment.
482    synthetic_create_depth: Option<usize>,
483    /// Address that reverted the call, if any.
484    pub reverter: Option<Address>,
485    /// LIFO stack tracking CREATE2 frames that were redirected to the CREATE2 factory.
486    pending_create2_redirects: Vec<PendingCreate2Redirect>,
487    /// LIFO stack tracking the effective address of traced calls delegated to the EVM provider.
488    pending_call_traces: Vec<PendingCallTrace>,
489    /// LIFO stack used to unwind snapshot restoration across reverted frames of the tracked
490    /// transaction: an isolated call, or the top-level transaction when isolation is disabled.
491    isolated_frame_checkpoints: Vec<IsolatedFrameCheckpoint>,
492    /// Pending CREATE2 deployer validation error, deferred from `frame_start` to `create` so
493    /// it goes through the normal inspector lifecycle (tracing, etc.).
494    pub pending_create2_error: Option<CreateOutcome>,
495    /// Counter for CREATE2 salt in `--batch` CREATE rewrites.
496    pub batch_create_counter: u64,
497    /// Whether the one-shot `--batch` rewrite warning has already been emitted.
498    pub batch_rewrite_warned: bool,
499    /// Per-inspector random seed mixed into `--batch` CREATE2 salts, ensuring re-runs
500    /// at identical on-chain state still produce distinct salts. Lazily initialized.
501    pub batch_rewrite_process_salt: Option<u64>,
502    /// Shared cancellation state for interruptible EVM execution.
503    execution_cancellation: Option<EvmExecutionCancellation>,
504    /// Amortizes deadline checks in the opcode hot path.
505    cancellation_poll_counter: u8,
506    /// Whether this inspector halted the current execution due to cancellation.
507    execution_cancelled: bool,
508    #[cfg(test)]
509    early_exit_test_gate: Option<EarlyExitTestGate>,
510    static_step_dispatch: OpcodeStepDispatch,
511    has_static_step_end_inspectors: bool,
512}
513
514/// Struct keeping mutable references to both parts of [InspectorStack] and implementing
515/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut].
516pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
517    pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
518    pub inner: &'a mut InspectorStackInner,
519}
520
521impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
522    fn with_nested_evm(
523        &mut self,
524        cheats: &mut Cheatcodes<FEN>,
525        ecx: &mut FoundryContextFor<'_, FEN>,
526        f: NestedEvmClosureFor<'_, FEN>,
527    ) -> Result<(), EVMError<DatabaseError>> {
528        let previous = self.synthetic_create_depth;
529        let create2_redirects = self.pending_create2_redirects.len();
530        let snapshot_checkpoints = self.isolated_frame_checkpoints.len();
531        let snapshot_restores = cheats.isolated_snapshot_restores.len();
532        let pending_snapshot_journal = cheats
533            .track_isolated_snapshots
534            .then(|| cheats.pending_isolated_snapshot_journal.clone());
535        let capture_snapshot_restore = cheats.capture_isolated_snapshot_restore;
536        self.synthetic_create_depth = (ecx.journal().depth() == 1).then_some(2);
537        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
538        let result = with_inherited_evm::<FEN::EvmFactory, _>(ecx, &mut inspector, f);
539        self.synthetic_create_depth = previous;
540        // Drop redirects left behind if nested execution aborted before their frames ended.
541        self.pending_create2_redirects.truncate(create2_redirects);
542        if result.is_err()
543            && let Some(pending_snapshot_journal) = pending_snapshot_journal
544        {
545            self.isolated_frame_checkpoints.truncate(snapshot_checkpoints);
546            cheats.isolated_snapshot_restores.truncate(snapshot_restores);
547            cheats.pending_isolated_snapshot_journal = pending_snapshot_journal;
548            cheats.capture_isolated_snapshot_restore = capture_snapshot_restore;
549        }
550        result
551    }
552
553    fn with_fresh_nested_evm(
554        &mut self,
555        cheats: &mut Cheatcodes<FEN>,
556        ecx: &mut FoundryContextFor<'_, FEN>,
557        chain_context: ChainFor<FEN>,
558        f: NestedEvmClosureFor<'_, FEN>,
559    ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
560        self.with_snapshot_tracking(cheats, |inner, cheats| {
561            let depth = ecx.journal().depth();
562            let previous_inner_context = inner.in_inner_context;
563            let previous_inner_context_data = inner.inner_context_data.replace(InnerContextData {
564                original_origin: ecx.tx().caller(),
565                locally_created_accounts: AddressHashSet::default(),
566                root_depth: depth,
567            });
568            let previous_precompile = inner.isolated_call_was_precompile.take();
569            inner.in_inner_context = true;
570            let mut evm_env = ecx.evm_clone();
571            let (db, _) = ecx.db_journal_inner_mut();
572            let inherited_disable_fee_charge = evm_env.cfg_env.disable_fee_charge;
573            if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
574                evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
575            }
576            let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
577            let mut evm = FEN::EvmFactory::default().create_nested_evm_with_inspector(
578                db,
579                evm_env,
580                &mut inspector,
581            );
582            *evm.chain_mut() = chain_context;
583            // The fresh root duplicates the suspended frame; its children share the existing
584            // tracer.
585            evm.journal_inner_mut().depth = depth;
586            let result = f(&mut *evm).map(|()| {
587                let mut evm_env = evm.to_evm_env();
588                evm_env.cfg_env.disable_fee_charge = inherited_disable_fee_charge;
589                evm_env
590            });
591            drop(evm);
592            inner.in_inner_context = previous_inner_context;
593            inner.inner_context_data = previous_inner_context_data;
594            inner.isolated_call_was_precompile = previous_precompile;
595            result
596        })
597    }
598
599    fn transact_on_db(
600        &mut self,
601        cheats: &mut Cheatcodes<FEN>,
602        ecx: &mut FoundryContextFor<'_, FEN>,
603        fork_id: Option<U256>,
604        transaction: B256,
605    ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>> {
606        self.with_snapshot_tracking(cheats, |inner, cheats| {
607            let mut evm_env = ecx.evm_clone();
608            if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
609                evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
610            }
611            let outer_tx_env = ecx.tx_clone();
612            let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
613            let (db, inner) = ecx.db_journal_inner_mut();
614            db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, &mut inspector)
615        })
616    }
617
618    fn transact_from_tx_on_db(
619        &mut self,
620        cheats: &mut Cheatcodes<FEN>,
621        ecx: &mut FoundryContextFor<'_, FEN>,
622        tx_env: TxEnvFor<FEN>,
623    ) -> eyre::Result<()> {
624        self.with_snapshot_tracking(cheats, |inner, cheats| {
625            let mut evm_env = ecx.evm_clone();
626            if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
627                evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
628            }
629            let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
630            let (db, inner) = ecx.db_journal_inner_mut();
631            db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
632        })
633    }
634
635    fn console_log(&mut self, msg: &str) {
636        if let Some(ref mut collector) = self.log_collector {
637            InspectorExt::console_log(&mut **collector, msg);
638        }
639    }
640
641    fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
642        self.tracer.as_deref_mut()
643    }
644}
645
646impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
647    fn default() -> Self {
648        Self::new()
649    }
650}
651
652impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
653    /// Creates a new inspector stack.
654    ///
655    /// Note that the stack is empty by default, and you must add inspectors to it.
656    /// This is done by calling the `set_*` methods on the stack directly, or by building the stack
657    /// with [`InspectorStack`].
658    #[inline]
659    pub fn new() -> Self {
660        Self { cheatcodes: None, inner: InspectorStackInner::default() }
661    }
662
663    /// Set the solar compiler instance.
664    #[inline]
665    pub fn set_analysis(&mut self, analysis: Analysis) {
666        self.analysis = Some(analysis);
667    }
668
669    /// Set the cancellation state checked during EVM execution.
670    #[inline]
671    pub(crate) fn set_early_exit(&mut self, early_exit: EarlyExit) {
672        self.set_execution_cancellation(EvmExecutionCancellation::early_exit(early_exit));
673    }
674
675    /// Set the complete cancellation state checked during EVM execution.
676    #[inline]
677    pub(crate) fn set_execution_cancellation(&mut self, cancellation: EvmExecutionCancellation) {
678        self.execution_cancellation = Some(cancellation);
679    }
680
681    /// Returns whether this inspector halted execution due to cancellation.
682    #[inline]
683    pub(crate) const fn execution_cancelled(&self) -> bool {
684        self.inner.execution_cancelled
685    }
686
687    #[cfg(test)]
688    pub(crate) fn set_early_exit_test_gate(
689        &mut self,
690        entered: std::sync::mpsc::Sender<()>,
691        release: std::sync::mpsc::Receiver<()>,
692        target_pc: usize,
693    ) {
694        self.early_exit_test_gate = Some(EarlyExitTestGate {
695            entered,
696            release: Arc::new(std::sync::Mutex::new(release)),
697            target_pc,
698            notified: Arc::new(std::sync::atomic::AtomicBool::new(false)),
699        });
700    }
701
702    /// Sets the block for the relevant inspectors.
703    #[inline]
704    pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
705        if let Some(cheatcodes) = &mut self.cheatcodes {
706            cheatcodes.block = Some(block);
707        }
708    }
709
710    /// Sets the gas price for the relevant inspectors.
711    #[inline]
712    pub fn set_gas_price(&mut self, gas_price: u128) {
713        if let Some(cheatcodes) = &mut self.cheatcodes {
714            cheatcodes.gas_price = Some(gas_price);
715        }
716    }
717
718    /// Set the cheatcodes inspector.
719    #[inline]
720    pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
721        self.cheatcodes = Some(cheatcodes.into());
722    }
723
724    /// Set the fuzzer inspector.
725    #[inline]
726    pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
727        self.fuzzer = Some(fuzzer.into());
728        self.refresh_static_step_dispatch();
729    }
730
731    /// Set the Chisel inspector.
732    #[inline]
733    pub fn set_chisel(&mut self, final_pc: usize) {
734        self.chisel_state = Some(ChiselState::new(final_pc).into());
735        self.refresh_static_step_end_dispatch();
736    }
737
738    /// Set whether to enable the line coverage collector.
739    #[inline]
740    pub fn collect_line_coverage(&mut self, yes: bool) {
741        self.line_coverage = yes.then(Default::default);
742        self.refresh_static_step_dispatch();
743    }
744
745    /// Set whether to enable the edge coverage collector with default config.
746    #[inline]
747    pub fn collect_edge_coverage(&mut self, yes: bool) {
748        self.edge_coverage = yes.then(|| EdgeCovInspector::default().into());
749        self.refresh_static_step_dispatch();
750    }
751
752    /// Enable edge coverage with an explicit configuration.
753    #[inline]
754    pub fn collect_edge_coverage_with_edge_config(&mut self, config: EdgeCovConfig) {
755        self.edge_coverage = Some(EdgeCovInspector::with_config(config).into());
756        self.refresh_static_step_dispatch();
757    }
758
759    /// Configure the edge coverage collector from a [`FuzzCorpusConfig`].
760    ///
761    /// Derives both the on/off gate and [`EdgeCovConfig`] from `corpus`.
762    #[inline]
763    pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
764        self.edge_coverage = corpus
765            .collect_evm_edge_coverage()
766            .then(|| EdgeCovInspector::with_config(corpus.into()).into());
767        self.refresh_static_step_dispatch();
768    }
769
770    /// Set whether to collect EVM comparison operands.
771    #[inline]
772    pub fn collect_evm_cmp_log(&mut self, yes: bool) {
773        if yes {
774            self.edge_coverage
775                .get_or_insert_with(|| EdgeCovInspector::with_cmp_log_only().into())
776                .enable_cmp_log(true);
777        } else if let Some(edge_coverage) = &mut self.edge_coverage {
778            edge_coverage.enable_cmp_log(false);
779        }
780        self.refresh_static_step_dispatch();
781    }
782
783    /// Set whether to collect sancov edge coverage from instrumented native crates.
784    #[inline]
785    pub const fn collect_sancov_edges(&mut self, yes: bool) {
786        self.inner.sancov_edges = yes;
787    }
788
789    /// Set whether to capture sancov trace-cmp operands for dictionary injection.
790    #[inline]
791    pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
792        self.inner.sancov_trace_cmp = yes;
793    }
794
795    /// Set whether to enable call isolation.
796    #[inline]
797    pub const fn enable_isolation(&mut self, yes: bool) {
798        self.inner.enable_isolation = yes;
799    }
800
801    /// Set networks with enabled features.
802    #[inline]
803    pub const fn networks(&mut self, networks: NetworkConfigs) {
804        self.inner.networks = networks;
805    }
806
807    /// Returns additional addresses installed and recognized as cheatcode contracts.
808    #[inline]
809    pub const fn extra_cheatcode_addresses(&self) -> &'static [Address] {
810        self.inner.extra_cheatcode_addresses
811    }
812
813    /// Sets additional addresses installed and recognized as cheatcode contracts.
814    #[inline]
815    pub const fn set_extra_cheatcode_addresses(&mut self, addresses: &'static [Address]) {
816        self.inner.extra_cheatcode_addresses = addresses;
817    }
818
819    /// Set the CREATE2 deployer address.
820    #[inline]
821    pub fn set_create2_deployer(&mut self, deployer: Address) {
822        self.create2_deployer = deployer;
823    }
824
825    /// Set whether to enable the log collector.
826    /// - None for no log collection.
827    /// - Some(true) for realtime console.log-ing.
828    /// - Some(false) for log collection.
829    #[inline]
830    pub fn collect_logs(&mut self, live_logs: Option<bool>) {
831        self.log_collector = live_logs.map(|live_logs| {
832            Box::new(if live_logs {
833                LogCollector::LiveLogs
834            } else {
835                LogCollector::Capture { logs: Vec::new() }
836            })
837        });
838    }
839
840    /// Set whether to enable the trace printer.
841    #[inline]
842    pub fn print(&mut self, yes: bool) {
843        self.printer = yes.then(Default::default);
844        self.refresh_static_opcode_dispatch();
845    }
846
847    /// Set trace data requirements.
848    #[inline]
849    pub fn tracing_requirements(&mut self, requirements: TraceRequirements) {
850        let config = requirements.into_config();
851        self.revert_diag = config.is_some().then(RevertDiagnostic::default).map(Into::into);
852
853        if let Some(config) = config {
854            *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
855        } else {
856            self.tracer = None;
857        }
858        self.refresh_static_opcode_dispatch();
859    }
860
861    /// Set whether to enable script execution inspector.
862    #[inline]
863    pub fn script(&mut self, script_address: Address) {
864        self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
865            script_address;
866        if let Some(cheatcodes) = &mut self.cheatcodes {
867            cheatcodes.script_address = Some(script_address);
868        }
869        self.refresh_static_step_dispatch();
870    }
871
872    #[inline(always)]
873    fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
874        InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
875    }
876
877    /// Collects all the data gathered during inspection into a single struct.
878    pub fn collect(self) -> InspectorData<FEN> {
879        let Self {
880            mut cheatcodes,
881            inner:
882                InspectorStackInner {
883                    chisel_state,
884                    line_coverage,
885                    edge_coverage,
886                    log_collector,
887                    tempo_labels,
888                    tracer,
889                    revert_diag,
890                    reverter,
891                    ..
892                },
893        } = self;
894
895        let trace_diagnostics =
896            revert_diag.map(|revert_diag| revert_diag.into_diagnostics()).unwrap_or_default();
897
898        let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
899            let ignored = cheatcodes
900                .as_mut()
901                .map(|cheatcodes| {
902                    let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);
903
904                    // If the last pause call was not resumed, ignore the rest of the trace
905                    if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
906                        ignored.insert(last_pause_call, (arena.nodes().len(), 0));
907                    }
908
909                    ignored
910                })
911                .unwrap_or_default();
912
913            SparsedTraceArena { arena, ignored, diagnostics: trace_diagnostics }
914        });
915
916        let (edge_coverage, evm_cmp_values) = edge_coverage
917            .map(|edge_coverage| {
918                let (hitcount, cmp_values) = edge_coverage.into_parts();
919                (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
920            })
921            .unwrap_or_default();
922
923        InspectorData {
924            logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
925            labels: {
926                let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default();
927                if let Some(tempo_labels) = tempo_labels {
928                    labels.extend(tempo_labels.labels);
929                }
930                labels
931            },
932            traces,
933            line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
934            edge_coverage,
935            evm_cmp_values,
936            cheatcodes,
937            chisel_state: chisel_state.and_then(|state| state.state),
938            reverter,
939        }
940    }
941}
942
943impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
944    fn finish_create2_redirect(&mut self, depth: usize, frame_result: &mut FrameResult) {
945        let Some(redirect) = self
946            .inner
947            .pending_create2_redirects
948            .last()
949            .copied()
950            .filter(|redirect| redirect.depth == depth)
951        else {
952            return;
953        };
954        self.inner.pending_create2_redirects.pop();
955
956        let FrameResult::Call(call) = frame_result else {
957            debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
958            return;
959        };
960
961        let address = match call.instruction_result() {
962            return_ok!() => Address::try_from(call.output().as_ref())
963                .map_err(|_| {
964                    call.result = InterpreterResult {
965                        result: InstructionResult::Revert,
966                        output: "invalid CREATE2 factory output".into(),
967                        gas: Gas::new(call.result.gas.limit()),
968                    };
969                })
970                .ok(),
971            _ => None,
972        };
973
974        *frame_result = FrameResult::Create(CreateOutcome {
975            result: call.result.clone(),
976            address,
977            charged_create_state_gas: redirect.charged_create_state_gas,
978        });
979    }
980
981    /// Adjusts the EVM data for the inner EVM context.
982    /// Should be called on the top-level call of inner context (depth == 0 &&
983    /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility
984    /// Updates tx.origin to the value before entering inner context
985    fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
986        let inner_context_data =
987            self.inner_context_data.as_ref().expect("should be called in inner context");
988        ecx.tx_mut().set_caller(inner_context_data.original_origin);
989    }
990
991    fn do_call_end(
992        &mut self,
993        ecx: &mut FoundryContextFor<'_, FEN>,
994        inputs: &CallInputs,
995        outcome: &mut CallOutcome,
996    ) {
997        let storage_hook_active =
998            self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
999        if !storage_hook_active && let Some(fuzzer) = &mut self.fuzzer {
1000            fuzzer.call_end(ecx, inputs, outcome);
1001        }
1002
1003        let result = outcome.result.result;
1004        call_inspectors!(
1005            #[ret]
1006            [&mut self.tracer, &mut self.cheatcodes, &mut self.printer, &mut self.revert_diag],
1007            |inspector| {
1008                let previous_output = outcome.output().clone();
1009                inspector.call_end(ecx, inputs, outcome);
1010
1011                // If the inspector returns a different status or a revert with a non-empty message,
1012                // we assume it wants to tell us something
1013                let different = outcome.result.result != result
1014                    || (outcome.result.result == InstructionResult::Revert
1015                        && outcome.output() != &previous_output);
1016                different.then_some(())
1017            },
1018        );
1019
1020        // Record first address that reverted the call.
1021        if result.is_revert() && self.reverter.is_none() {
1022            self.reverter = Some(inputs.transfer_to());
1023        }
1024    }
1025
1026    fn do_create_end(
1027        &mut self,
1028        ecx: &mut FoundryContextFor<'_, FEN>,
1029        call: &CreateInputs,
1030        outcome: &mut CreateOutcome,
1031    ) {
1032        let result = outcome.result.result;
1033        call_inspectors!(
1034            #[ret]
1035            [&mut self.line_coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer],
1036            |inspector| {
1037                let previous_output = outcome.output().clone();
1038                inspector.create_end(ecx, call, outcome);
1039
1040                // If the inspector returns a different status or a revert with a non-empty message,
1041                // we assume it wants to tell us something
1042                let different = outcome.result.result != result
1043                    || (outcome.result.result == InstructionResult::Revert
1044                        && outcome.output() != &previous_output);
1045                different.then_some(())
1046            },
1047        );
1048    }
1049
1050    fn transact_inner(
1051        &mut self,
1052        ecx: &mut FoundryContextFor<'_, FEN>,
1053        kind: TxKind,
1054        caller: Address,
1055        input: Bytes,
1056        gas: IsolatedGas,
1057        value: U256,
1058    ) -> (InterpreterResult, Option<Address>, bool) {
1059        let IsolatedGas { regular_limit, reservoir, precharged_state } = gas;
1060        let source_fork_id = ecx.db().active_fork_id();
1061        let cached_evm_env = ecx.evm_clone();
1062        let cached_tx_env = ecx.tx_clone();
1063        self.isolated_call_was_precompile = None;
1064
1065        ecx.block_mut().set_basefee(0);
1066        ecx.cfg_env_mut().disable_fee_charge = true;
1067
1068        let chain_id = ecx.cfg().chain_id();
1069        ecx.tx_mut().set_chain_id(Some(chain_id));
1070        ecx.tx_mut().set_caller(caller);
1071        ecx.tx_mut().set_kind(kind);
1072        ecx.tx_mut().set_data(input);
1073        ecx.tx_mut().set_enveloped_tx(Bytes::new());
1074        ecx.tx_mut().set_value(value);
1075        let initial_gas = calculate_initial_gas(ecx.tx(), ecx.cfg());
1076        // Preserve the frame's regular gas and reservoir across the synthetic transaction
1077        // boundary. The extra state gas offsets the account-creation charge already paid by the
1078        // outer opcode.
1079        let regular_gas_limit = regular_limit.saturating_add(initial_gas.initial_total_gas());
1080        ecx.cfg_env_mut().tx_gas_limit_cap = Some(regular_gas_limit);
1081        // revm rejects transactions whose gas limit is below the EIP-7623 calldata floor. Raising
1082        // the limit would give the frame more gas than it was forwarded, so skip that check and
1083        // apply the floor to the transaction result instead.
1084        let floor_gas =
1085            (initial_gas.floor_gas() > regular_gas_limit).then_some(initial_gas.floor_gas());
1086        if floor_gas.is_some() {
1087            ecx.cfg_env_mut().disable_eip7623 = true;
1088        }
1089        let mut tx_gas_limit = regular_gas_limit.saturating_add(reservoir);
1090        if let Some(precharged_state) = precharged_state {
1091            tx_gas_limit = tx_gas_limit.saturating_add(precharged_state);
1092        }
1093        ecx.tx_mut().set_gas_limit(tx_gas_limit);
1094
1095        // If we haven't disabled gas limit checks, ensure that transaction gas limit will not
1096        // exceed block gas limit.
1097        if !ecx.cfg().is_block_gas_limit_disabled() {
1098            let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
1099            ecx.tx_mut().set_gas_limit(gas_limit);
1100        }
1101        ecx.tx_mut().set_gas_price(0);
1102        // If the cached tx is EIP-4844 (e.g. set by `vm.blobhashes`), downgrade
1103        // it to EIP-1559 and drop the blob hashes so revm doesn't reject the
1104        // synthetic inner tx because `gas_price = 0` plus blob fields fail
1105        // 4844 validation. The contract-visible `BLOBHASH` opcode is restored
1106        // via `EnvOverrides`. Other types (incl. EIP-2930 access lists) are
1107        // intentionally left intact, and the full original tx is restored
1108        // from `cached_tx_env` after the inner call.
1109        if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
1110            ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
1111            ecx.tx_mut().set_blob_hashes(Vec::new());
1112        }
1113
1114        let locally_created_accounts = ecx
1115            .journal()
1116            .evm_state()
1117            .iter()
1118            .filter_map(|(addr, acc)| acc.is_created_locally().then_some(*addr))
1119            .collect();
1120        let root_depth = ecx.journal().depth();
1121        self.inner_context_data = Some(InnerContextData {
1122            original_origin: cached_tx_env.caller(),
1123            locally_created_accounts,
1124            root_depth,
1125        });
1126        self.outer_disable_fee_charge = Some(cached_evm_env.cfg_env.disable_fee_charge);
1127        self.in_inner_context = true;
1128
1129        // Tell cheatcodes we're entering the synthetic inner transaction so
1130        // env-mutating cheatcodes route through `env_overrides` instead of
1131        // fighting with the fee-accounting zeroing above. See `EnvOverrides`.
1132        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1133            cheats.pending_isolated_snapshot_journal = None;
1134            cheats.track_isolated_snapshots = true;
1135            cheats.isolated_snapshot_restores.clear();
1136            cheats.in_isolation_context = true;
1137        }
1138        self.inner.isolated_frame_checkpoints.clear();
1139
1140        let evm_env = ecx.evm_clone();
1141        let tx_env = ecx.tx_clone();
1142        let factory = FEN::EvmFactory::default();
1143        let chain_context = ecx.chain().clone();
1144
1145        let isolated_state = prepare_child_state(ecx.journal_inner());
1146
1147        #[cfg(feature = "monad")]
1148        let state = foundry_evm_core::FoundryJournal::capture_reserve_balance(ecx.journal());
1149        #[cfg(feature = "monad")]
1150        let mut reserve_balance = None;
1151        let mut nested_chain_context = None;
1152        let res = self.with_inspector(|mut inspector| {
1153            let (res, nested_env) = {
1154                let (db, _) = ecx.db_journal_inner_mut();
1155                let mut evm = factory.create_nested_evm_with_inspector(db, evm_env, &mut inspector);
1156                *evm.chain_mut() = chain_context;
1157                evm.journal_inner_mut().state = isolated_state;
1158                #[cfg(feature = "monad")]
1159                {
1160                    foundry_evm_core::FoundryJournal::restore_reserve_balance(
1161                        evm.journal_mut(),
1162                        state,
1163                    );
1164                    foundry_evm_core::evm::refresh_nested_chain_journal(&mut *evm);
1165                    foundry_evm_core::FoundryJournal::set_preserve_reserve_balance(
1166                        evm.journal_mut(),
1167                        true,
1168                    );
1169                }
1170                // Preserve the surrounding trace depth, including a synthetic CREATE frame.
1171                evm.journal_inner_mut().depth = root_depth;
1172                let res = evm.transact_raw(tx_env);
1173                nested_chain_context = Some(evm.chain_mut().clone());
1174                #[cfg(feature = "monad")]
1175                {
1176                    reserve_balance =
1177                        Some(foundry_evm_core::FoundryJournal::capture_reserve_balance(
1178                            evm.journal_mut(),
1179                        ));
1180                }
1181                (res, evm.to_evm_env())
1182            };
1183
1184            // Restore env, preserving cheatcode cfg/block changes from the nested EVM but restoring
1185            // the original tx and temporary fee overrides used for the nested call.
1186            let mut restored_evm_env = nested_env;
1187            restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1188            restored_evm_env.cfg_env.disable_fee_charge = cached_evm_env.cfg_env.disable_fee_charge;
1189            restored_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1190            restored_evm_env.cfg_env.disable_eip7623 = cached_evm_env.cfg_env.disable_eip7623;
1191            ecx.set_evm(restored_evm_env);
1192            ecx.set_tx(cached_tx_env);
1193
1194            res
1195        });
1196        *ecx.chain_mut() = nested_chain_context.expect("nested EVM chain context was captured");
1197
1198        self.in_inner_context = false;
1199        self.inner_context_data = None;
1200        self.outer_disable_fee_charge = None;
1201
1202        // Reset the cheatcodes isolation flag now that the synthetic inner
1203        // transaction has finished.
1204        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1205            cheats.in_isolation_context = false;
1206            cheats.track_isolated_snapshots = false;
1207        }
1208
1209        let mut gas = Gas::new_with_regular_gas_and_reservoir(regular_limit, reservoir);
1210        let was_precompile_called = self.isolated_call_was_precompile.take().unwrap_or(false);
1211
1212        let Ok(res) = res else {
1213            if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1214                cheats.pending_isolated_snapshot_journal = None;
1215                cheats.isolated_snapshot_restores.clear();
1216            }
1217            self.inner.isolated_frame_checkpoints.clear();
1218            #[cfg(feature = "monad")]
1219            foundry_evm_core::FoundryJournal::restore_reserve_balance(
1220                ecx.journal_mut(),
1221                reserve_balance.expect("isolated transaction state was captured"),
1222            );
1223            #[cfg(feature = "monad")]
1224            refresh_chain_journal(ecx);
1225            // Should we match, encode and propagate error as a revert reason?
1226            let result =
1227                InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
1228            return (result, None, was_precompile_called);
1229        };
1230
1231        let mut transaction_gas = *res.result.gas();
1232        if let Some(floor_gas) = floor_gas {
1233            transaction_gas = transaction_gas.with_floor_gas(floor_gas);
1234        }
1235        let tx_state_gas_used = transaction_gas.block_state_gas_used();
1236        // A failed frame refunds the account-creation charge to the outer opcode, and its
1237        // transaction does not charge that state gas either.
1238        let precharged_state = precharged_state.map_or(0, |gas| gas.min(tx_state_gas_used));
1239        let state_gas_used = tx_state_gas_used - precharged_state;
1240        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1241            cheats
1242                .gas_metering
1243                .set_isolated_snapshot_gas_used(transaction_gas.tx_gas_used(), precharged_state);
1244        }
1245        // The transaction also pays its intrinsic gas and calldata floor, which can exceed the
1246        // gas forwarded to the frame. The frame then consumes its whole regular budget, as on an
1247        // exceptional halt.
1248        if !(gas.record_state_cost(state_gas_used)
1249            && gas.record_regular_cost(transaction_gas.block_regular_gas_used()))
1250        {
1251            gas.spend_all();
1252        }
1253
1254        let rolled_back = !res.result.is_success();
1255
1256        let restored_journal = self
1257            .cheatcodes
1258            .as_deref_mut()
1259            .and_then(|cheats| cheats.pending_isolated_snapshot_journal.take());
1260        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1261            cheats.isolated_snapshot_restores.clear();
1262        }
1263        self.inner.isolated_frame_checkpoints.clear();
1264        let restored_snapshot = if !rolled_back && let Some(restored_journal) = restored_journal {
1265            let (_, journaled_state) = ecx.db_journal_inner_mut();
1266            journaled_state.journal = restored_journal;
1267            true
1268        } else {
1269            false
1270        };
1271        if source_fork_id == ecx.db().active_fork_id() {
1272            merge_child_state(ecx.journal_mut().evm_state_mut(), res.state, restored_snapshot);
1273        } else {
1274            *ecx.journal_mut().evm_state_mut() = res.state;
1275        }
1276        #[cfg(feature = "monad")]
1277        foundry_evm_core::FoundryJournal::restore_reserve_balance(
1278            ecx.journal_mut(),
1279            reserve_balance.expect("isolated transaction state was captured"),
1280        );
1281        #[cfg(feature = "monad")]
1282        refresh_chain_journal(ecx);
1283
1284        let (result, address, output) = match res.result {
1285            ExecutionResult::Success { reason, gas: _, logs: _, output } => {
1286                gas.set_refund(transaction_gas.final_refunded() as i64);
1287                let address = match output {
1288                    Output::Create(_, address) => address,
1289                    Output::Call(_) => None,
1290                };
1291                (reason.into(), address, output.into_data())
1292            }
1293            ExecutionResult::Halt { reason, .. } => {
1294                (InstructionResult::from(reason), None, Bytes::new())
1295            }
1296            ExecutionResult::Revert { output, .. } => (InstructionResult::Revert, None, output),
1297        };
1298        #[cfg(feature = "monad")]
1299        if rolled_back {
1300            refresh_chain_journal(ecx);
1301        }
1302        (InterpreterResult { result, output, gas }, address, was_precompile_called)
1303    }
1304
1305    /// Moves out of references, constructs a new [`InspectorStackRefMut`] and runs the given
1306    /// closure with it.
1307    fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
1308        let mut cheatcodes = self
1309            .cheatcodes
1310            .as_deref_mut()
1311            .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone())));
1312        let mut inner = std::mem::take(self.inner);
1313
1314        // Save pending CREATE2 redirects so frame_end in the nested EVM doesn't consume them.
1315        // These belong to the outer EVM's frame lifecycle and must be restored after.
1316        let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);
1317
1318        let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });
1319
1320        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1321            *cheats = cheatcodes.unwrap();
1322        }
1323
1324        inner.pending_create2_redirects = saved_create2_redirects;
1325        *self.inner = inner;
1326
1327        out
1328    }
1329
1330    /// Invoked at the beginning of a new top-level (0 depth) frame.
1331    fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1332        self.locally_created_accounts.clear();
1333        self.top_level_frame_failed_before_rewrite = false;
1334        warm_arbitrum_system_contract::<FEN>(ecx);
1335        if let Some(cheatcodes) = &mut self.cheatcodes {
1336            cheatcodes.clear_storage_hook_mapping_slots();
1337        }
1338
1339        if self.enable_isolation {
1340            // If we're in isolation mode, we need to keep track of the state at the beginning of
1341            // the frame to be able to roll back on revert
1342            self.top_frame_journal.clone_from(ecx.journal().evm_state());
1343        }
1344    }
1345
1346    /// Invoked at the end of root frame.
1347    fn top_level_frame_end(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, failed: bool) {
1348        if let Some(cheatcodes) = &mut self.cheatcodes {
1349            cheatcodes.clear_storage_hook_mapping_slots();
1350        }
1351        if !failed {
1352            return;
1353        }
1354        // The frame was rolled back. Since cheatcodes may have altered the EVM state in a way
1355        // that violates some constraints, e.g. `deal`, restore those changes explicitly.
1356        if let Some(cheats) = self.cheatcodes.as_mut() {
1357            cheats.on_revert(ecx);
1358        }
1359
1360        // In isolation mode, restore the state from before the root frame. We cannot rely on
1361        // revm's journal because it does not account for changes made by isolated calls.
1362        if self.enable_isolation {
1363            *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
1364        }
1365
1366        #[cfg(feature = "monad")]
1367        refresh_chain_journal(ecx);
1368    }
1369
1370    // We take extra care in optimizing `step` and `step_end`, as they're are likely the most
1371    // hot functions in all of Foundry.
1372    // We want to `#[inline(always)]` these functions so that `InspectorStack` does not
1373    // delegate to `InspectorStackRefMut` in this case.
1374
1375    #[inline(always)]
1376    fn step_inlined(
1377        &mut self,
1378        interpreter: &mut Interpreter,
1379        ecx: &mut FoundryContextFor<'_, FEN>,
1380    ) {
1381        let storage_hook_active = if let Some(cheats) = self.cheatcodes.as_mut() {
1382            if cheats.has_storage_hooks() && cheats.finish_storage_hook_callback(interpreter, ecx) {
1383                return;
1384            }
1385            cheats.pc = interpreter.bytecode.pc();
1386            cheats.is_storage_hook_active()
1387        } else {
1388            false
1389        };
1390
1391        #[cfg(test)]
1392        if interpreter.bytecode.opcode() == op::JUMPDEST
1393            && let Some(gate) = &self.inner.early_exit_test_gate
1394            && interpreter.bytecode.pc() == gate.target_pc
1395            && !gate.notified.swap(true, std::sync::atomic::Ordering::Relaxed)
1396        {
1397            let _ = gate.entered.send(());
1398            let _ = gate
1399                .release
1400                .lock()
1401                .expect("early-exit test gate lock poisoned")
1402                .recv_timeout(std::time::Duration::from_secs(1));
1403        }
1404
1405        if let Some(cancellation) = &self.inner.execution_cancellation {
1406            let poll_deadline = self.inner.cancellation_poll_counter == 0;
1407            self.inner.cancellation_poll_counter =
1408                self.inner.cancellation_poll_counter.wrapping_add(1);
1409            if cancellation.should_stop(poll_deadline) {
1410                self.inner.execution_cancelled = true;
1411                interpreter.halt(InstructionResult::Stop);
1412                return;
1413            }
1414        }
1415
1416        match self.static_step_dispatch {
1417            OpcodeStepDispatch::None => {}
1418            OpcodeStepDispatch::FuzzerOnly => {
1419                if !storage_hook_active && let Some(inspector) = &mut self.fuzzer {
1420                    inspector.step(interpreter, ecx);
1421                }
1422            }
1423            OpcodeStepDispatch::General => {
1424                if !storage_hook_active {
1425                    call_inspectors!(
1426                        [
1427                            // These are sorted in definition order.
1428                            &mut self.edge_coverage,
1429                            &mut self.fuzzer,
1430                            &mut self.line_coverage,
1431                        ],
1432                        |inspector| (**inspector).step(interpreter, ecx),
1433                    );
1434                }
1435                call_inspectors!(
1436                    [
1437                        // These are sorted in definition order.
1438                        &mut self.printer,
1439                        &mut self.revert_diag,
1440                        &mut self.script_execution_inspector,
1441                        &mut self.tracer,
1442                    ],
1443                    |inspector| (**inspector).step(interpreter, ecx),
1444                );
1445            }
1446        }
1447
1448        if let Some(cheats) = self.cheatcodes.as_mut()
1449            && cheats.has_step_hooks()
1450        {
1451            let opcode = interpreter.bytecode.opcode();
1452            if !cheats.has_recording_accesses_only_step_hook()
1453                || matches!(opcode, op::SLOAD | op::SSTORE)
1454            {
1455                crate::utils::cold_path();
1456                cheats.step(interpreter, ecx);
1457            }
1458        }
1459    }
1460
1461    #[inline(always)]
1462    fn step_end_inlined(
1463        &mut self,
1464        interpreter: &mut Interpreter,
1465        ecx: &mut FoundryContextFor<'_, FEN>,
1466    ) {
1467        if self.has_static_step_end_inspectors {
1468            call_inspectors!(
1469                [
1470                    // These are sorted in definition order.
1471                    &mut self.chisel_state,
1472                    &mut self.printer,
1473                    &mut self.revert_diag,
1474                    &mut self.tracer,
1475                ],
1476                |inspector| (**inspector).step_end(interpreter, ecx),
1477            );
1478        }
1479
1480        if let Some(fuzzer) = &mut self.fuzzer
1481            && fuzzer.mapping_slots.is_some()
1482        {
1483            fuzzer.step_end(interpreter, ecx);
1484        }
1485
1486        if let Some(cheats) = self.cheatcodes.as_mut()
1487            && cheats.has_step_end_hooks()
1488        {
1489            crate::utils::cold_path();
1490            cheats.step_end(interpreter, ecx);
1491        }
1492    }
1493
1494    fn finish_isolated_snapshot_frame(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1495        let Some(frame) = self.inner.isolated_frame_checkpoints.pop() else { return };
1496
1497        if frame.failed
1498            && let Some(cheats) = self.cheatcodes.as_deref_mut()
1499            && cheats.isolated_snapshot_restores.len() > frame.restore_len
1500        {
1501            cheats.isolated_snapshot_restores.truncate(frame.restore_len + 1);
1502            let mut journal = cheats.isolated_snapshot_restores.pop().unwrap();
1503            journal.checkpoint_revert(frame.checkpoint);
1504            journal.depth = frame.return_depth;
1505            if cheats.isolated_snapshot_restores.is_empty() {
1506                cheats.pending_isolated_snapshot_journal = None;
1507            }
1508            ecx.set_journal_inner(journal);
1509        }
1510
1511        // The caller only needs the first restoration made since it started, so drop the ones a
1512        // finished call captured after it. This bounds their count by call depth; each retained
1513        // journal can still contain all loaded account state.
1514        if let Some(caller) = self.inner.isolated_frame_checkpoints.last()
1515            && let Some(cheats) = self.cheatcodes.as_deref_mut()
1516        {
1517            cheats.isolated_snapshot_restores.truncate(caller.restore_len + 1);
1518        }
1519
1520        if let Some(cheats) = self.cheatcodes.as_deref_mut()
1521            && cheats.pending_isolated_snapshot_journal.is_some()
1522        {
1523            cheats.pending_isolated_snapshot_journal = Some(ecx.journal_inner().journal.clone());
1524        }
1525    }
1526}
1527
1528impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
1529    for InspectorStackRefMut<'_, FEN>
1530{
1531    fn initialize_interp(
1532        &mut self,
1533        interpreter: &mut Interpreter,
1534        ecx: &mut FoundryContextFor<'_, FEN>,
1535    ) {
1536        if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
1537            && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
1538            && frame.is_create
1539            && let Some(offset) = ecx.journal_inner().journal[frame.checkpoint.journal_i..]
1540                .iter()
1541                .position(|entry| matches!(entry, JournalEntry::AccountCreated { .. }))
1542        {
1543            frame.checkpoint.journal_i += offset;
1544        }
1545
1546        let address = interpreter.input.target_address();
1547        let should_mark_created_locally = self.locally_created_accounts.contains(&address)
1548            || self
1549                .inner_context_data
1550                .as_ref()
1551                .is_some_and(|ctx| ctx.locally_created_accounts.contains(&address));
1552        if should_mark_created_locally
1553            && let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&address)
1554            && account.is_created()
1555        {
1556            account.mark_created_locally();
1557        }
1558
1559        call_inspectors!(
1560            [
1561                &mut self.line_coverage,
1562                &mut self.tracer,
1563                &mut self.cheatcodes,
1564                &mut self.script_execution_inspector,
1565                &mut self.printer
1566            ],
1567            |inspector| inspector.initialize_interp(interpreter, ecx),
1568        );
1569    }
1570
1571    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1572        self.step_inlined(interpreter, ecx);
1573    }
1574
1575    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1576        self.step_end_inlined(interpreter, ecx);
1577    }
1578
1579    #[allow(clippy::redundant_clone)]
1580    fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1581        call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1582            inspector.log(ecx, log.clone())
1583        });
1584        if let Some(inspector) = &mut self.cheatcodes
1585            && inspector.has_log_hooks()
1586        {
1587            crate::utils::cold_path();
1588            inspector.log(ecx, log.clone());
1589        }
1590        call_inspectors!([&mut self.printer], |inspector| { inspector.log(ecx, log.clone()) });
1591    }
1592
1593    #[allow(clippy::redundant_clone)]
1594    fn log_full(
1595        &mut self,
1596        interpreter: &mut Interpreter,
1597        ecx: &mut FoundryContextFor<'_, FEN>,
1598        log: Log,
1599    ) {
1600        call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1601            inspector.log_full(interpreter, ecx, log.clone())
1602        });
1603        if let Some(inspector) = &mut self.cheatcodes
1604            && inspector.has_log_hooks()
1605        {
1606            crate::utils::cold_path();
1607            inspector.log_full(interpreter, ecx, log.clone());
1608        }
1609        call_inspectors!([&mut self.printer], |inspector| {
1610            inspector.log_full(interpreter, ecx, log.clone())
1611        });
1612    }
1613
1614    fn frame_start(
1615        &mut self,
1616        ecx: &mut FoundryContextFor<'_, FEN>,
1617        frame_input: &mut FrameInput,
1618    ) -> Option<FrameResult> {
1619        // A non-isolated top-level transaction needs the same restore tracking as an isolated call.
1620        if !self.enable_isolation
1621            && !self.in_inner_context
1622            && ecx.journal().depth() == 0
1623            && let Some(cheats) = self.cheatcodes.as_deref_mut()
1624        {
1625            cheats.pending_isolated_snapshot_journal = None;
1626            cheats.track_isolated_snapshots = true;
1627            cheats.isolated_snapshot_restores.clear();
1628            self.inner.isolated_frame_checkpoints.clear();
1629        }
1630        if let Some(cheats) = self.cheatcodes.as_deref_mut()
1631            && cheats.track_isolated_snapshots
1632        {
1633            let journal = ecx.journal_inner();
1634            let restore_len = cheats.isolated_snapshot_restores.len();
1635            // Unwinding a frame only needs the first restoration made inside it, so a restoration
1636            // requested by this call is captured only if none was captured since its caller
1637            // started.
1638            cheats.capture_isolated_snapshot_restore = self
1639                .inner
1640                .isolated_frame_checkpoints
1641                .last()
1642                .is_none_or(|caller| caller.restore_len == restore_len);
1643            self.inner.isolated_frame_checkpoints.push(IsolatedFrameCheckpoint {
1644                checkpoint: JournalCheckpoint {
1645                    log_i: journal.logs.len(),
1646                    journal_i: journal.journal.len(),
1647                    selfdestructed_i: journal.selfdestructed_addresses.len(),
1648                },
1649                restore_len,
1650                return_depth: journal.depth,
1651                is_create: matches!(frame_input, FrameInput::Create(_)),
1652                failed: false,
1653            });
1654        }
1655
1656        if let FrameInput::Create(inputs) = frame_input
1657            && self.should_use_create2_factory(ecx.journal().depth(), inputs)
1658        {
1659            // `get_create2_factory_call_inputs` forwards `inputs.value()` to the factory
1660            // as `CallValue::Transfer`, and the default factory runtime forwards CALLVALUE
1661            // into CREATE2, so payable deploys are supported on both the explicit
1662            // `new C{salt, value}` path and the `--batch` rewrite path.
1663            let salt = match inputs.scheme() {
1664                CreateScheme::Create2 { salt } => salt,
1665                // --batch: process_salt (random per run) + counter make each deploy unique.
1666                // The nonce below is from the EVM frame caller (script contract), not the EOA.
1667                CreateScheme::Create => {
1668                    if !self.inner.batch_rewrite_warned {
1669                        let _ = sh_warn!(
1670                            "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
1671                             deployed addresses follow the CREATE2 formula and constructor \
1672                             msg.sender is the factory, not the EOA."
1673                        );
1674                        self.inner.batch_rewrite_warned = true;
1675                    }
1676                    let chain_id = ecx.cfg().chain_id();
1677                    let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
1678                    self.inner.next_batch_create_salt(chain_id, nonce)
1679                }
1680                _ => return None,
1681            };
1682
1683            let gas_limit = inputs.gas_limit();
1684            let create2_deployer = self.create2_deployer();
1685
1686            // Validate deployer before rewriting.
1687            let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
1688            if code_hash == KECCAK_EMPTY {
1689                // Store the revert so `create` can return it inside the normal inspector
1690                // lifecycle (avoids tracing mismatch from short-circuiting in frame_start).
1691                self.inner.pending_create2_error = Some(CreateOutcome {
1692                    result: InterpreterResult {
1693                        result: InstructionResult::Revert,
1694                        output: Bytes::from(
1695                            format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
1696                        ),
1697                        gas: Gas::new(gas_limit),
1698                    },
1699                    address: None,
1700                    charged_create_state_gas: inputs.charged_create_state_gas(),
1701                });
1702                return None;
1703            } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
1704                self.inner.pending_create2_error = Some(CreateOutcome {
1705                    result: InterpreterResult {
1706                        result: InstructionResult::Revert,
1707                        output: "invalid CREATE2 deployer bytecode".into(),
1708                        gas: Gas::new(gas_limit),
1709                    },
1710                    address: None,
1711                    charged_create_state_gas: inputs.charged_create_state_gas(),
1712                });
1713                return None;
1714            }
1715
1716            let call_inputs =
1717                get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
1718                    .ok()?;
1719
1720            // Record the redirect depth *after* validation succeeds.
1721            self.inner.pending_create2_redirects.push(PendingCreate2Redirect {
1722                depth: ecx.journal().depth(),
1723                charged_create_state_gas: inputs.charged_create_state_gas(),
1724            });
1725
1726            // Rewrite the frame input from Create to Call.
1727            *frame_input = FrameInput::Call(Box::new(call_inputs));
1728        }
1729
1730        None
1731    }
1732
1733    fn frame_end(
1734        &mut self,
1735        ecx: &mut FoundryContextFor<'_, FEN>,
1736        _frame_input: &FrameInput,
1737        frame_result: &mut FrameResult,
1738    ) {
1739        let depth = ecx.journal().depth();
1740        self.finish_create2_redirect(depth, frame_result);
1741        if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots) {
1742            self.finish_isolated_snapshot_frame(ecx);
1743        }
1744
1745        let result = frame_result.instruction_result();
1746        if !self.in_inner_context && depth == 0 {
1747            let failed = std::mem::take(&mut self.inner.top_level_frame_failed_before_rewrite)
1748                || !result.is_ok();
1749            self.top_level_frame_end(ecx, failed);
1750            if !self.enable_isolation {
1751                if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1752                    cheats.track_isolated_snapshots = false;
1753                    cheats.isolated_snapshot_restores.clear();
1754                }
1755                self.inner.isolated_frame_checkpoints.clear();
1756            }
1757        }
1758    }
1759
1760    fn call(
1761        &mut self,
1762        ecx: &mut FoundryContextFor<'_, FEN>,
1763        call: &mut CallInputs,
1764    ) -> Option<CallOutcome> {
1765        if self.is_inner_context_root(ecx.journal().depth()) {
1766            self.adjust_evm_data_for_inner_context(ecx);
1767            warm_arbitrum_system_contract::<FEN>(ecx);
1768            // Transaction call inputs use the original target, so resolve function mocks here too.
1769            if let Some(cheatcodes) = self.cheatcodes.as_deref() {
1770                apply_mocked_function(cheatcodes, ecx, call);
1771            }
1772            return None;
1773        }
1774
1775        if ecx.journal().depth() == 0 {
1776            self.top_level_frame_start(ecx);
1777        }
1778
1779        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1780            revert_diag.frame_start();
1781        }
1782
1783        let storage_hook_callback = self
1784            .cheatcodes
1785            .as_deref()
1786            .is_some_and(|cheatcodes| cheatcodes.is_storage_hook_callback(ecx, call));
1787        let storage_hook_active =
1788            self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
1789
1790        if !storage_hook_active {
1791            call_inspectors!(
1792                #[ret]
1793                [&mut self.fuzzer],
1794                |inspector| {
1795                    let mut out = None;
1796                    if let Some(output) = inspector.call(ecx, call) {
1797                        out = Some(Some(output));
1798                    }
1799                    out
1800                }
1801            );
1802        }
1803
1804        if self.tracer.is_some() {
1805            crate::utils::cold_path();
1806            let (output, trace_idx) = {
1807                let tracer = self.tracer.as_deref_mut().unwrap();
1808                let output = tracer.call(ecx, call);
1809                (output, tracer.traces().nodes().len() - 1)
1810            };
1811            self.pending_call_traces.push(PendingCallTrace { trace_idx, executed_address: None });
1812            if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1813                revert_diag.set_trace_node(trace_idx);
1814            }
1815            if output.is_some() {
1816                return output;
1817            }
1818        }
1819
1820        call_inspectors!(
1821            #[ret]
1822            [
1823                &mut self.log_collector,
1824                &mut self.printer,
1825                &mut self.revert_diag,
1826                &mut self.tempo_labels
1827            ],
1828            |inspector| inspector.call(ecx, call).map(Some),
1829        );
1830
1831        // Storage hook callbacks are instrumentation frames, not user calls. Let revm execute the
1832        // callback after tracing it, but do not apply mocks, pranks, broadcasts, or isolation.
1833        if storage_hook_callback {
1834            if let Some(pending) = self.pending_call_traces.last_mut() {
1835                pending.executed_address = Some(call.bytecode_address);
1836            }
1837            return None;
1838        }
1839
1840        // The tracer records call inputs before cheatcodes apply caller overrides such as pranks
1841        // and broadcasts. Keep the trace lifecycle ordering, but remember the node so its caller
1842        // can be synchronized with the inputs that are actually executed.
1843        let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
1844        // Also isolate a synthetic `deployCode` create that was rewritten to a CREATE2 factory
1845        // call.
1846        let isolate = self.enable_isolation
1847            && !self.in_inner_context
1848            && (ecx.journal().depth() == 1
1849                || self.inner.synthetic_create_depth == Some(ecx.journal().depth()));
1850        let mut cheatcode_outcome = None;
1851        if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1852            apply_mocked_function(cheatcodes, ecx, call);
1853
1854            let execution_disable_fee_charge = ecx.cfg_env().disable_fee_charge;
1855            if let Some(disable_fee_charge) = self.inner.outer_disable_fee_charge {
1856                ecx.cfg_env_mut().disable_fee_charge = disable_fee_charge;
1857            }
1858            cheatcode_outcome = cheatcodes.call_with_executor(
1859                ecx,
1860                call,
1861                self.inner,
1862                isolate && call.scheme.is_call(),
1863            );
1864            ecx.cfg_env_mut().disable_fee_charge = execution_disable_fee_charge;
1865        }
1866
1867        if let Some(trace_idx) = trace_idx
1868            && let Some(tracer) = self.tracer.as_deref_mut()
1869        {
1870            let caller = match call.scheme {
1871                CallScheme::DelegateCall | CallScheme::CallCode => call.transfer_to(),
1872                CallScheme::Call | CallScheme::StaticCall => call.transfer_from(),
1873            };
1874            let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
1875            debug_assert_eq!(node.trace.depth, ecx.journal().depth());
1876            node.trace.caller = caller;
1877        }
1878
1879        if let Some(output) = cheatcode_outcome {
1880            return Some(output);
1881        }
1882
1883        if let Some(outcome) = handle_arbitrum_system_call::<FEN>(ecx, call) {
1884            return Some(outcome);
1885        }
1886
1887        if let Some(pending) = self.pending_call_traces.last_mut() {
1888            pending.executed_address = Some(call.bytecode_address);
1889        }
1890
1891        if isolate {
1892            match call.scheme {
1893                // Isolate CALLs
1894                CallScheme::Call => {
1895                    let input = call.input.bytes(ecx);
1896                    let precharged_state = call
1897                        .charged_new_account_state_gas
1898                        .then_some(ecx.cfg().gas_params().new_account_state_gas());
1899                    let (result, _, was_precompile_called) = self.transact_inner(
1900                        ecx,
1901                        TxKind::Call(call.transfer_to()),
1902                        call.transfer_from(),
1903                        input,
1904                        IsolatedGas {
1905                            regular_limit: call.gas_limit,
1906                            reservoir: call.reservoir,
1907                            precharged_state,
1908                        },
1909                        call.value.get(),
1910                    );
1911                    return Some(CallOutcome {
1912                        result,
1913                        memory_offset: call.return_memory_offset.clone(),
1914                        was_precompile_called,
1915                        precompile_call_logs: vec![],
1916                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1917                    });
1918                }
1919                // Mark accounts and storage cold before STATICCALLs
1920                CallScheme::StaticCall => {
1921                    let (_, journal_inner) = ecx.db_journal_inner_mut();
1922                    let JournaledState { state, warm_addresses, .. } = journal_inner;
1923                    for (addr, acc_mut) in state {
1924                        // Do not mark accounts and storage cold accounts with arbitrary storage.
1925                        if let Some(cheatcodes) = &self.cheatcodes
1926                            && cheatcodes.has_arbitrary_storage(addr)
1927                        {
1928                            continue;
1929                        }
1930
1931                        if warm_addresses.is_cold(addr) {
1932                            acc_mut.mark_cold();
1933                        }
1934
1935                        for slot_mut in acc_mut.storage.values_mut() {
1936                            slot_mut.is_cold = true;
1937                        }
1938                    }
1939                }
1940                // Process other variants as usual
1941                CallScheme::CallCode | CallScheme::DelegateCall => {}
1942            }
1943        }
1944
1945        None
1946    }
1947
1948    fn call_end(
1949        &mut self,
1950        ecx: &mut FoundryContextFor<'_, FEN>,
1951        inputs: &CallInputs,
1952        outcome: &mut CallOutcome,
1953    ) {
1954        if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
1955            && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
1956        {
1957            frame.failed |= !outcome.result.result.is_ok();
1958        }
1959
1960        // We are processing inner context outputs in the outer context, so need to avoid processing
1961        // twice.
1962        if self.is_inner_context_root(ecx.journal().depth()) {
1963            self.isolated_call_was_precompile = Some(outcome.was_precompile_called);
1964            return;
1965        }
1966
1967        if ecx.journal().depth() == 0 {
1968            self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
1969        }
1970
1971        if let Some(pending) = self.pending_call_traces.pop()
1972            && let Some(tracer) = self.tracer.as_deref_mut()
1973        {
1974            let trace = &mut tracer.traces_mut().nodes_mut()[pending.trace_idx].trace;
1975            if trace.address == P256_VERIFY {
1976                trace.maybe_precompile = Some(
1977                    pending.executed_address == Some(P256_VERIFY) && outcome.was_precompile_called,
1978                );
1979            }
1980        }
1981
1982        self.do_call_end(ecx, inputs, outcome);
1983
1984        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1985            revert_diag.frame_end();
1986        }
1987    }
1988
1989    fn create(
1990        &mut self,
1991        ecx: &mut FoundryContextFor<'_, FEN>,
1992        create: &mut CreateInputs,
1993    ) -> Option<CreateOutcome> {
1994        if self.is_inner_context_root(ecx.journal().depth()) {
1995            self.adjust_evm_data_for_inner_context(ecx);
1996            warm_arbitrum_system_contract::<FEN>(ecx);
1997            return None;
1998        }
1999
2000        if ecx.journal().depth() == 0 {
2001            self.top_level_frame_start(ecx);
2002        }
2003
2004        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2005            revert_diag.frame_start();
2006        }
2007
2008        if self.tracer.is_some() {
2009            crate::utils::cold_path();
2010            let (output, trace_idx) = {
2011                let tracer = self.tracer.as_deref_mut().unwrap();
2012                let output = tracer.create(ecx, create);
2013                (output, tracer.traces().nodes().len() - 1)
2014            };
2015            if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2016                revert_diag.set_trace_node(trace_idx);
2017            }
2018            if output.is_some() {
2019                return output;
2020            }
2021        }
2022
2023        call_inspectors!(
2024            #[ret]
2025            [&mut self.line_coverage],
2026            |inspector| inspector.create(ecx, create).map(Some),
2027        );
2028
2029        let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
2030        let mut cheatcode_outcome = None;
2031        if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
2032            cheatcode_outcome = cheatcodes.create(ecx, create);
2033        }
2034
2035        if let Some(trace_idx) = trace_idx
2036            && let Some(tracer) = self.tracer.as_deref_mut()
2037        {
2038            let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
2039            debug_assert_eq!(node.trace.depth, ecx.journal().depth());
2040            node.trace.caller = create.caller();
2041        }
2042
2043        if let Some(output) = cheatcode_outcome {
2044            return Some(output);
2045        }
2046
2047        // If frame_start detected an invalid CREATE2 deployer, return the error here
2048        // (after sub-inspectors have been notified) so tracing stays balanced.
2049        if let Some(error) = self.inner.pending_create2_error.take() {
2050            return Some(error);
2051        }
2052
2053        if !matches!(create.scheme(), CreateScheme::Create2 { .. })
2054            && self.enable_isolation
2055            && !self.in_inner_context
2056            && (ecx.journal().depth() == 1
2057                || self.synthetic_create_depth == Some(ecx.journal().depth()))
2058        {
2059            // In isolation mode, transact_inner returns None for the address on revert; pre-compute
2060            // the would-be deployed address so create_end can enforce expected_revert reverter
2061            // checks.
2062            let precomputed_address = ecx
2063                .journal()
2064                .evm_state()
2065                .get(&create.caller())
2066                .map(|acc| create.caller().create(acc.info.nonce));
2067            let precharged_state = create
2068                .charged_create_state_gas()
2069                .then_some(ecx.cfg().gas_params().create_state_gas());
2070
2071            let (result, address, _) = self.transact_inner(
2072                ecx,
2073                TxKind::Create,
2074                create.caller(),
2075                create.init_code().clone(),
2076                IsolatedGas {
2077                    regular_limit: create.gas_limit(),
2078                    reservoir: create.reservoir(),
2079                    precharged_state,
2080                },
2081                create.value(),
2082            );
2083            let address =
2084                address.or_else(|| if result.is_revert() { precomputed_address } else { None });
2085            return Some(CreateOutcome {
2086                result,
2087                address,
2088                charged_create_state_gas: create.charged_create_state_gas(),
2089            });
2090        }
2091
2092        None
2093    }
2094
2095    fn create_end(
2096        &mut self,
2097        ecx: &mut FoundryContextFor<'_, FEN>,
2098        call: &CreateInputs,
2099        outcome: &mut CreateOutcome,
2100    ) {
2101        if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
2102            && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
2103        {
2104            frame.failed |= !outcome.result.result.is_ok();
2105        }
2106
2107        if outcome.result.result.is_ok()
2108            && let Some(address) = outcome.address
2109        {
2110            self.locally_created_accounts.insert(address);
2111
2112            if self.in_inner_context
2113                && let Some(inner_context) = &mut self.inner_context_data
2114            {
2115                inner_context.locally_created_accounts.insert(address);
2116            }
2117        }
2118
2119        // We are processing inner context outputs in the outer context, so need to avoid processing
2120        // twice.
2121        if self.is_inner_context_root(ecx.journal().depth()) {
2122            return;
2123        }
2124
2125        if ecx.journal().depth() == 0 {
2126            self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
2127        }
2128
2129        self.do_create_end(ecx, call, outcome);
2130
2131        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2132            revert_diag.frame_end();
2133        }
2134    }
2135
2136    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2137        call_inspectors!([&mut self.printer], |inspector| {
2138            Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2139                inspector, contract, target, value,
2140            )
2141        });
2142    }
2143}
2144
2145fn warm_arbitrum_system_contract<FEN: FoundryEvmNetwork>(ecx: &mut FoundryContextFor<'_, FEN>) {
2146    if !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id()) {
2147        return;
2148    }
2149
2150    // Nitro warms ArbSys like a precompile. Use the access list so other selectors can still
2151    // execute code at the address.
2152    let mut access_list = ecx.journal_inner().warm_addresses.access_list().clone();
2153    access_list.entry(arbitrum::ARB_SYS_ADDRESS).or_default();
2154    ecx.journal_mut().warm_access_list(access_list);
2155}
2156
2157fn handle_arbitrum_system_call<FEN: FoundryEvmNetwork>(
2158    ecx: &mut FoundryContextFor<'_, FEN>,
2159    call: &CallInputs,
2160) -> Option<CallOutcome> {
2161    if call.transfer_to() != arbitrum::ARB_SYS_ADDRESS
2162        || call.bytecode_address != arbitrum::ARB_SYS_ADDRESS
2163        || !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id())
2164    {
2165        return None;
2166    }
2167
2168    let input = call.input.bytes(ecx);
2169    if input.get(..4) != Some(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR) {
2170        return None;
2171    }
2172
2173    // Forks report their L2 block, which the block env lacks on Arbitrum because `NUMBER` returns
2174    // the L1 block there. Without a fork the block env is read here to follow `vm.roll`.
2175    let block_number = match ecx.db().active_fork_block_number() {
2176        Some(block_number) => block_number,
2177        None => ecx.block().number().saturating_to(),
2178    };
2179    let Some((gas_cost, output)) = arbitrum::arb_block_number_call(call.gas_limit, block_number)
2180    else {
2181        return Some(arbitrum_call_outcome(
2182            call,
2183            InstructionResult::PrecompileOOG,
2184            0,
2185            Bytes::new(),
2186        ));
2187    };
2188
2189    Some(arbitrum_call_outcome(call, InstructionResult::Return, gas_cost, output))
2190}
2191
2192fn arbitrum_call_outcome(
2193    call: &CallInputs,
2194    result: InstructionResult,
2195    gas_used: u64,
2196    output: Bytes,
2197) -> CallOutcome {
2198    let mut gas = Gas::new(call.gas_limit);
2199    if result.is_ok() {
2200        let _ = gas.record_regular_cost(gas_used);
2201    } else {
2202        gas.spend_all();
2203    }
2204
2205    CallOutcome {
2206        result: InterpreterResult { result, output, gas },
2207        memory_offset: call.return_memory_offset.clone(),
2208        was_precompile_called: true,
2209        precompile_call_logs: vec![],
2210        charged_new_account_state_gas: call.charged_new_account_state_gas,
2211    }
2212}
2213
2214impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
2215    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2216        call_inspectors!(
2217            #[ret]
2218            [&mut self.cheatcodes],
2219            |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
2220        );
2221
2222        false
2223    }
2224
2225    fn console_log(&mut self, msg: &str) {
2226        call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
2227            inspector, msg
2228        ));
2229    }
2230
2231    fn get_networks(&self) -> NetworkConfigs {
2232        self.inner.networks
2233    }
2234
2235    fn create2_deployer(&self) -> Address {
2236        self.inner.create2_deployer
2237    }
2238}
2239
2240impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
2241    #[inline(always)]
2242    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2243        self.as_mut().step_inlined(interpreter, ecx)
2244    }
2245
2246    #[inline(always)]
2247    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2248        self.as_mut().step_end_inlined(interpreter, ecx)
2249    }
2250
2251    fn call(
2252        &mut self,
2253        context: &mut FoundryContextFor<'_, FEN>,
2254        inputs: &mut CallInputs,
2255    ) -> Option<CallOutcome> {
2256        self.as_mut().call(context, inputs)
2257    }
2258
2259    fn call_end(
2260        &mut self,
2261        context: &mut FoundryContextFor<'_, FEN>,
2262        inputs: &CallInputs,
2263        outcome: &mut CallOutcome,
2264    ) {
2265        self.as_mut().call_end(context, inputs, outcome)
2266    }
2267
2268    fn create(
2269        &mut self,
2270        context: &mut FoundryContextFor<'_, FEN>,
2271        create: &mut CreateInputs,
2272    ) -> Option<CreateOutcome> {
2273        self.as_mut().create(context, create)
2274    }
2275
2276    fn create_end(
2277        &mut self,
2278        context: &mut FoundryContextFor<'_, FEN>,
2279        call: &CreateInputs,
2280        outcome: &mut CreateOutcome,
2281    ) {
2282        self.as_mut().create_end(context, call, outcome)
2283    }
2284
2285    fn initialize_interp(
2286        &mut self,
2287        interpreter: &mut Interpreter,
2288        ecx: &mut FoundryContextFor<'_, FEN>,
2289    ) {
2290        self.as_mut().initialize_interp(interpreter, ecx)
2291    }
2292
2293    fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2294        self.as_mut().log(ecx, log)
2295    }
2296
2297    fn log_full(
2298        &mut self,
2299        interpreter: &mut Interpreter,
2300        ecx: &mut FoundryContextFor<'_, FEN>,
2301        log: Log,
2302    ) {
2303        self.as_mut().log_full(interpreter, ecx, log)
2304    }
2305
2306    fn frame_start(
2307        &mut self,
2308        context: &mut FoundryContextFor<'_, FEN>,
2309        frame_input: &mut FrameInput,
2310    ) -> Option<FrameResult> {
2311        self.as_mut().frame_start(context, frame_input)
2312    }
2313
2314    fn frame_end(
2315        &mut self,
2316        context: &mut FoundryContextFor<'_, FEN>,
2317        frame_input: &FrameInput,
2318        frame_result: &mut FrameResult,
2319    ) {
2320        self.as_mut().frame_end(context, frame_input, frame_result)
2321    }
2322
2323    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2324        call_inspectors!([&mut self.inner.printer], |inspector| {
2325            Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2326                inspector, contract, target, value,
2327            )
2328        });
2329    }
2330}
2331
2332impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
2333    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2334        self.as_mut().should_use_create2_factory(depth, inputs)
2335    }
2336
2337    fn get_networks(&self) -> NetworkConfigs {
2338        self.networks
2339    }
2340
2341    fn create2_deployer(&self) -> Address {
2342        self.create2_deployer
2343    }
2344}
2345
2346impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
2347    type Target = &'a mut InspectorStackInner;
2348
2349    fn deref(&self) -> &Self::Target {
2350        &self.inner
2351    }
2352}
2353
2354impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
2355    fn deref_mut(&mut self) -> &mut Self::Target {
2356        &mut self.inner
2357    }
2358}
2359
2360impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
2361    type Target = InspectorStackInner;
2362
2363    fn deref(&self) -> &Self::Target {
2364        &self.inner
2365    }
2366}
2367
2368impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
2369    fn deref_mut(&mut self) -> &mut Self::Target {
2370        &mut self.inner
2371    }
2372}
2373
2374impl InspectorStackInner {
2375    #[inline]
2376    const fn refresh_static_opcode_dispatch(&mut self) {
2377        self.refresh_static_step_dispatch();
2378        self.refresh_static_step_end_dispatch();
2379    }
2380
2381    #[inline]
2382    const fn refresh_static_step_dispatch(&mut self) {
2383        self.static_step_dispatch = if self.edge_coverage.is_none()
2384            && self.line_coverage.is_none()
2385            && self.printer.is_none()
2386            && self.revert_diag.is_none()
2387            && self.script_execution_inspector.is_none()
2388            && self.tracer.is_none()
2389        {
2390            if self.fuzzer.is_some() {
2391                OpcodeStepDispatch::FuzzerOnly
2392            } else {
2393                OpcodeStepDispatch::None
2394            }
2395        } else {
2396            OpcodeStepDispatch::General
2397        };
2398    }
2399
2400    #[inline]
2401    const fn refresh_static_step_end_dispatch(&mut self) {
2402        self.has_static_step_end_inspectors = self.chisel_state.is_some()
2403            || self.printer.is_some()
2404            || self.revert_diag.is_some()
2405            || self.tracer.is_some();
2406    }
2407
2408    /// Derive the next `--batch` CREATE2 salt and advance the per-batch counter.
2409    /// The per-inspector random seed is lazily initialized on first use.
2410    fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
2411        let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
2412        let counter = self.batch_create_counter;
2413        self.batch_create_counter = counter.wrapping_add(1);
2414        compute_batch_create_salt(process_salt, chain_id, nonce, counter)
2415    }
2416
2417    /// Whether this frame duplicates the outer frame that entered an isolated transaction.
2418    fn is_inner_context_root(&self, depth: usize) -> bool {
2419        self.in_inner_context
2420            && self.inner_context_data.as_ref().is_some_and(|inner| depth == inner.root_depth)
2421    }
2422
2423    /// Gives a fresh transaction its own restore tracking, independent of the suspended parent.
2424    fn with_snapshot_tracking<FEN: FoundryEvmNetwork, R>(
2425        &mut self,
2426        cheats: &mut Cheatcodes<FEN>,
2427        f: impl FnOnce(&mut Self, &mut Cheatcodes<FEN>) -> R,
2428    ) -> R {
2429        let tracking = std::mem::replace(&mut cheats.track_isolated_snapshots, true);
2430        let isolation = std::mem::replace(&mut cheats.in_isolation_context, false);
2431        let capture = std::mem::replace(&mut cheats.capture_isolated_snapshot_restore, true);
2432        let restores = std::mem::take(&mut cheats.isolated_snapshot_restores);
2433        let pending = cheats.pending_isolated_snapshot_journal.take();
2434        let frames = std::mem::take(&mut self.isolated_frame_checkpoints);
2435        let result = f(self, cheats);
2436        self.isolated_frame_checkpoints = frames;
2437        cheats.pending_isolated_snapshot_journal = pending;
2438        cheats.isolated_snapshot_restores = restores;
2439        cheats.capture_isolated_snapshot_restore = capture;
2440        cheats.in_isolation_context = isolation;
2441        cheats.track_isolated_snapshots = tracking;
2442        result
2443    }
2444}
2445
2446/// Derive the CREATE2 salt used by `--batch` CREATE rewrites.
2447///
2448/// Mixes a per-inspector random seed, the chain id, the caller nonce, and a per-batch counter
2449/// so that two simulations launched at the same on-chain state produce distinct salts and
2450/// do not collide at the Arachnid factory.
2451fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
2452    let mut buf = [0u8; 32];
2453    buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
2454    buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
2455    buf[16..24].copy_from_slice(&nonce.to_be_bytes());
2456    buf[24..32].copy_from_slice(&counter.to_be_bytes());
2457    keccak256(buf).into()
2458}
2459
2460/// Redirects `call` to the implementation set with `vm.mockFunction`, if one matches.
2461fn apply_mocked_function<FEN: FoundryEvmNetwork>(
2462    cheatcodes: &Cheatcodes<FEN>,
2463    ecx: &mut FoundryContextFor<'_, FEN>,
2464    call: &mut CallInputs,
2465) {
2466    if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) {
2467        let input_bytes = call.input.bytes(ecx);
2468        // Check if any mock function set for call data or if catch-all mock function set
2469        // for selector.
2470        if let Some(target) = mocks
2471            .get(&input_bytes)
2472            .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
2473        {
2474            call.bytecode_address = *target;
2475
2476            let target =
2477                ecx.journal_mut().load_account_with_code(*target).expect("failed to load account");
2478            call.known_bytecode =
2479                (target.info.code_hash(), target.info.code.clone().unwrap_or_default());
2480        }
2481    }
2482}
2483
2484#[cfg(test)]
2485mod tests {
2486    use super::*;
2487
2488    #[test]
2489    fn opcode_dispatch_defaults_to_no_static_inspectors() {
2490        let stack = InspectorStackInner::default();
2491
2492        assert_eq!(stack.static_step_dispatch, OpcodeStepDispatch::None);
2493        assert!(!stack.has_static_step_end_inspectors);
2494    }
2495
2496    #[test]
2497    fn opcode_dispatch_uses_fuzzer_fast_path_when_fuzzer_is_only_static_step_inspector() {
2498        let mut stack = InspectorStack::<EthEvmNetwork>::new();
2499        stack.set_fuzzer(Fuzzer::new(16, None));
2500
2501        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2502        assert!(!stack.inner.has_static_step_end_inspectors);
2503
2504        stack.collect_line_coverage(true);
2505        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2506
2507        stack.collect_line_coverage(false);
2508        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2509    }
2510
2511    #[test]
2512    fn opcode_dispatch_tracks_general_step_and_step_end_inspectors() {
2513        let mut stack = InspectorStack::<EthEvmNetwork>::new();
2514
2515        stack.print(true);
2516        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2517        assert!(stack.inner.has_static_step_end_inspectors);
2518
2519        stack.print(false);
2520        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2521        assert!(!stack.inner.has_static_step_end_inspectors);
2522
2523        stack.tracing_requirements(TraceRequirements::none().with_calls(true));
2524        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2525        assert!(stack.inner.has_static_step_end_inspectors);
2526
2527        stack.tracing_requirements(TraceRequirements::none());
2528        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2529        assert!(!stack.inner.has_static_step_end_inspectors);
2530
2531        stack.set_chisel(0);
2532        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2533        assert!(stack.inner.has_static_step_end_inspectors);
2534    }
2535
2536    #[test]
2537    fn opcode_dispatch_tracks_script_and_edge_coverage_inspectors() {
2538        let mut stack = InspectorStack::<EthEvmNetwork>::new();
2539
2540        stack.script(Address::with_last_byte(1));
2541        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2542        assert!(!stack.inner.has_static_step_end_inspectors);
2543
2544        let mut stack = InspectorStack::<EthEvmNetwork>::new();
2545        stack.collect_edge_coverage(true);
2546        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2547        stack.collect_edge_coverage(false);
2548        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2549    }
2550
2551    #[test]
2552    fn revert_diagnostic_frames_remain_balanced() {
2553        let mut inspector = RevertDiagnostic::default();
2554        inspector.frame_start();
2555        inspector.set_trace_node(0);
2556        inspector.frame_start();
2557        inspector.set_trace_node(1);
2558        inspector.frame_end();
2559        inspector.frame_end();
2560
2561        assert!(inspector.into_diagnostics().is_empty());
2562    }
2563
2564    #[test]
2565    fn distinct_salts_across_simulations_at_same_nonce() {
2566        // Two "simulations" with different per-inspector seeds but identical on-chain state.
2567        let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
2568        let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
2569        assert_ne!(a, b);
2570    }
2571
2572    #[test]
2573    fn counter_changes_salt() {
2574        let a = compute_batch_create_salt(1, 1, 5, 0);
2575        let b = compute_batch_create_salt(1, 1, 5, 1);
2576        assert_ne!(a, b);
2577    }
2578
2579    #[test]
2580    fn chain_id_changes_salt() {
2581        let a = compute_batch_create_salt(1, 1, 5, 0);
2582        let b = compute_batch_create_salt(1, 2, 5, 0);
2583        assert_ne!(a, b);
2584    }
2585
2586    #[test]
2587    fn deterministic_for_same_inputs() {
2588        let a = compute_batch_create_salt(42, 1, 5, 7);
2589        let b = compute_batch_create_salt(42, 1, 5, 7);
2590        assert_eq!(a, b);
2591    }
2592
2593    #[test]
2594    fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
2595        // Two fresh inspectors fed identical (chain_id, nonce) must produce CREATE2 addresses
2596        // that differ when routed through the same factory with the same init code.
2597        let factory = Address::with_last_byte(0x42);
2598        let init_code = b"\x60\x80\x60\x40".as_slice();
2599
2600        let mut a = InspectorStackInner::default();
2601        let mut b = InspectorStackInner::default();
2602        let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2603        let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2604
2605        let addr_a = factory.create2_from_code(salt_a, init_code);
2606        let addr_b = factory.create2_from_code(salt_b, init_code);
2607        assert_ne!(addr_a, addr_b);
2608    }
2609}