Skip to main content

foundry_evm/inspectors/
stack.rs

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