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