Skip to main content

foundry_evm/inspectors/
stack.rs

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