Skip to main content

foundry_evm/inspectors/
stack.rs

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