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