use super::{
    Cheatcodes, CheatsConfig, ChiselState, CmpOperands, CustomPrintTracer, EdgeCovConfig,
    EdgeCovInspector, EdgeCoverage, Fuzzer, LineCoverageCollector, LogCollector, RevertDiagnostic,
    ScriptExecutionInspector, TempoLabels, TracingInspector,
};
use alloy_primitives::{
    Address, B256, Bytes, Log, TxKind, U256, keccak256,
    map::{AddressHashMap, AddressHashSet, AddressMap},
};

use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosure, Wallets};
use foundry_common::{compile::Analysis, sh_warn};
use foundry_config::FuzzCorpusConfig;
use foundry_evm_core::{
    FoundryBlock, FoundryTransaction, InspectorExt,
    backend::{DatabaseError, DatabaseExt, JournaledState},
    constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH,
    env::FoundryContextExt,
    evm::{
        BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory,
        FoundryEvmNetwork, SpecFor, TxEnvFor, get_create2_factory_call_inputs, with_cloned_context,
    },
};
use foundry_evm_coverage::HitMaps;
use foundry_evm_networks::{NetworkConfigs, arbitrum};
use foundry_evm_traces::{SparsedTraceArena, TraceRequirements};
use revm::{
    Inspector,
    context::{
        Block, Cfg, ContextTr, JournalTr, Transaction, TransactionType,
        result::{EVMError, ExecutionResult, Output},
    },
    context_interface::CreateScheme,
    handler::FrameResult,
    interpreter::{
        CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
        InstructionResult, Interpreter, InterpreterResult,
        bytecode::opcode as op,
        interpreter_types::{InputsTr, Jumps},
        return_ok,
    },
    primitives::KECCAK_EMPTY,
    state::{Account, AccountStatus},
};
use std::{
    ops::{Deref, DerefMut},
    sync::Arc,
};

use crate::executors::{EarlyExit, EvmExecutionCancellation};

#[derive(Clone, Debug)]
#[must_use = "builders do nothing unless you call `build` on them"]
pub struct InspectorStackBuilder<BLOCK: Clone> {
    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities.
    pub analysis: Option<Analysis>,
    /// The block environment.
    ///
    /// Used in the cheatcode handler to overwrite the block environment separately from the
    /// execution block environment.
    pub block: Option<BLOCK>,
    /// The gas price.
    ///
    /// Used in the cheatcode handler to overwrite the gas price separately from the gas price
    /// in the execution environment.
    pub gas_price: Option<u128>,
    /// The cheatcodes config.
    pub cheatcodes: Option<Arc<CheatsConfig>>,
    /// The fuzzer inspector and its state, if it exists.
    pub fuzzer: Option<Fuzzer>,
    /// Whether to enable tracing and revert diagnostics.
    pub trace_requirements: TraceRequirements,
    /// Whether logs should be collected.
    /// - None for no log collection.
    /// - Some(true) for realtime console.log-ing.
    /// - Some(false) for log collection.
    pub logs: Option<bool>,
    /// Whether line coverage info should be collected.
    pub line_coverage: Option<bool>,
    /// Whether to print all opcode traces into the console. Useful for debugging the EVM.
    pub print: Option<bool>,
    /// The chisel state inspector.
    pub chisel_state: Option<usize>,
    /// Whether to enable call isolation.
    /// In isolation mode all top-level calls are executed as a separate transaction in a separate
    /// EVM context, enabling more precise gas accounting and transaction state changes.
    pub enable_isolation: bool,
    /// Networks with enabled features.
    pub networks: NetworkConfigs,
    /// The wallets to set in the cheatcodes context.
    pub wallets: Option<Wallets>,
    /// The CREATE2 deployer address.
    pub create2_deployer: Address,
}

impl<BLOCK: Clone> Default for InspectorStackBuilder<BLOCK> {
    fn default() -> Self {
        Self {
            analysis: None,
            block: None,
            gas_price: None,
            cheatcodes: None,
            fuzzer: None,
            trace_requirements: TraceRequirements::none(),
            logs: None,
            line_coverage: None,
            print: None,
            chisel_state: None,
            enable_isolation: false,
            networks: NetworkConfigs::default(),
            wallets: None,
            create2_deployer: Default::default(),
        }
    }
}

impl<BLOCK: Clone> InspectorStackBuilder<BLOCK> {
    /// Create a new inspector stack builder.
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the solar compiler instance that grants syntactic and semantic analysis capabilities
    #[inline]
    pub fn set_analysis(mut self, analysis: Analysis) -> Self {
        self.analysis = Some(analysis);
        self
    }

    /// Set the block environment.
    #[inline]
    pub fn block(mut self, block: BLOCK) -> Self {
        self.block = Some(block);
        self
    }

    /// Set the gas price.
    #[inline]
    pub const fn gas_price(mut self, gas_price: u128) -> Self {
        self.gas_price = Some(gas_price);
        self
    }

    /// Enable cheatcodes with the given config.
    #[inline]
    pub fn cheatcodes(mut self, config: Arc<CheatsConfig>) -> Self {
        self.cheatcodes = Some(config);
        self
    }

    /// Set the wallets.
    #[inline]
    pub fn wallets(mut self, wallets: Wallets) -> Self {
        self.wallets = Some(wallets);
        self
    }

    /// Set the fuzzer inspector.
    #[inline]
    pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self {
        self.fuzzer = Some(fuzzer);
        self
    }

    /// Set the Chisel inspector.
    #[inline]
    pub const fn chisel_state(mut self, final_pc: usize) -> Self {
        self.chisel_state = Some(final_pc);
        self
    }

    /// Set the log collector, and whether to print the logs directly to stdout.
    #[inline]
    pub const fn logs(mut self, live_logs: bool) -> Self {
        self.logs = Some(live_logs);
        self
    }

    /// Set whether to collect line coverage information.
    #[inline]
    pub const fn line_coverage(mut self, yes: bool) -> Self {
        self.line_coverage = Some(yes);
        self
    }

    /// Set whether to enable the trace printer.
    #[inline]
    pub const fn print(mut self, yes: bool) -> Self {
        self.print = Some(yes);
        self
    }

    /// Set trace data requirements.
    #[inline]
    pub const fn trace_requirements(mut self, requirements: TraceRequirements) -> Self {
        self.trace_requirements = self.trace_requirements.merge(requirements);
        self
    }

    /// Set whether to enable the call isolation.
    /// For description of call isolation, see [`InspectorStack::enable_isolation`].
    #[inline]
    pub const fn enable_isolation(mut self, yes: bool) -> Self {
        self.enable_isolation = yes;
        self
    }

    /// Set networks with enabled features.
    #[inline]
    pub const fn networks(mut self, networks: NetworkConfigs) -> Self {
        self.networks = networks;
        self
    }

    #[inline]
    pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self {
        self.create2_deployer = create2_deployer;
        self
    }

    /// Builds the stack of inspectors to use when transacting/committing on the EVM.
    pub fn build<FEN: FoundryEvmNetwork<EvmFactory: FoundryEvmFactory<BlockEnv = BLOCK>>>(
        self,
    ) -> InspectorStack<FEN> {
        let Self {
            analysis,
            block,
            gas_price,
            cheatcodes,
            fuzzer,
            trace_requirements,
            logs,
            line_coverage,
            print,
            chisel_state,
            enable_isolation,
            networks,
            wallets,
            create2_deployer,
        } = self;
        let mut stack = InspectorStack::new();

        // inspectors
        if let Some(config) = cheatcodes {
            let mut cheatcodes = Cheatcodes::new(config);
            // Set analysis capabilities if they are provided
            if let Some(analysis) = analysis {
                stack.set_analysis(analysis.clone());
                cheatcodes.set_analysis(CheatcodeAnalysis::new(analysis));
            }
            // Set wallets if they are provided
            if let Some(wallets) = wallets {
                cheatcodes.set_wallets(wallets);
            }
            stack.set_cheatcodes(cheatcodes);
        }

        if let Some(fuzzer) = fuzzer {
            stack.set_fuzzer(fuzzer);
        }
        if let Some(chisel_state) = chisel_state {
            stack.set_chisel(chisel_state);
        }
        stack.collect_line_coverage(line_coverage.unwrap_or(false));
        stack.collect_logs(logs);
        stack.print(print.unwrap_or(false));
        stack.tracing_requirements(trace_requirements);

        stack.enable_isolation(enable_isolation);
        stack.networks(networks);
        stack.set_create2_deployer(create2_deployer);

        if networks.is_tempo() {
            stack.inner.tempo_labels = Some(Box::default());
        }

        // environment, must come after all of the inspectors
        if let Some(block) = block {
            stack.set_block(block);
        }
        if let Some(gas_price) = gas_price {
            stack.set_gas_price(gas_price);
        }

        stack
    }
}

/// Helper macro to call the same method on multiple inspectors without resorting to dynamic
/// dispatch.
#[macro_export]
macro_rules! call_inspectors {
    ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {
        $(
            if let Some($id) = $inspector {
                $crate::utils::cold_path();
                $body;
            }
        )+
    };
    (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{
        $(
            if let Some($id) = $inspector {
                $crate::utils::cold_path();
                if let Some(result) = $body {
                    return result;
                }
            }
        )+
    }};
}

/// The collected results of [`InspectorStack`].
pub struct InspectorData<FEN: FoundryEvmNetwork> {
    pub logs: Vec<Log>,
    pub labels: AddressHashMap<String>,
    pub traces: Option<SparsedTraceArena>,
    pub line_coverage: Option<HitMaps>,
    pub edge_coverage: Option<EdgeCoverage>,
    pub evm_cmp_values: Option<Vec<CmpOperands>>,
    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
    pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
    pub reverter: Option<Address>,
}

/// Contains data about the state of outer/main EVM which created and invoked the inner EVM context.
/// Used to adjust EVM state while in inner context.
///
/// We need this to avoid breaking changes due to EVM behavior differences in isolated vs
/// non-isolated mode. For descriptions and workarounds for those changes see: <https://github.com/foundry-rs/foundry/pull/7186#issuecomment-1959102195>
#[derive(Debug, Clone)]
pub struct InnerContextData {
    /// Origin of the transaction in the outer EVM context.
    original_origin: Address,
    /// Accounts that were created locally before entering the nested EVM context.
    locally_created_accounts: AddressHashSet,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum OpcodeStepDispatch {
    #[default]
    None,
    FuzzerOnly,
    General,
}

/// An inspector that calls multiple inspectors in sequence.
///
/// If a call to an inspector returns a value (indicating a stop or revert) the remaining inspectors
/// are not called.
///
/// Stack is divided into [Cheatcodes] and `InspectorStackInner`. This is done to allow assembling
/// `InspectorStackRefMut` inside [Cheatcodes] to allow usage of it as [revm::Inspector]. This gives
/// us ability to create and execute separate EVM frames from inside cheatcodes while still having
/// access to entire stack of inspectors and correctly handling traces, logs, debugging info
/// collection, etc.
#[derive(Clone, Debug)]
pub struct InspectorStack<FEN: FoundryEvmNetwork = EthEvmNetwork> {
    #[allow(clippy::type_complexity)]
    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
    pub inner: InspectorStackInner,
}

#[cfg(test)]
#[derive(Clone, Debug)]
struct EarlyExitTestGate {
    entered: std::sync::mpsc::Sender<()>,
    release: Arc<std::sync::Mutex<std::sync::mpsc::Receiver<()>>>,
    target_pc: usize,
    notified: Arc<std::sync::atomic::AtomicBool>,
}

/// All used inspectors besides [Cheatcodes].
///
/// See [`InspectorStack`].
#[derive(Default, Clone, Debug)]
pub struct InspectorStackInner {
    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities.
    pub analysis: Option<Analysis>,

    // Inspectors.
    // These are boxed to reduce the size of the struct and slightly improve performance of the
    // `if let Some` checks.
    pub chisel_state: Option<Box<ChiselState>>,
    pub edge_coverage: Option<Box<EdgeCovInspector>>,
    pub fuzzer: Option<Box<Fuzzer>>,
    pub line_coverage: Option<Box<LineCoverageCollector>>,
    pub log_collector: Option<Box<LogCollector>>,
    pub printer: Option<Box<CustomPrintTracer>>,
    pub revert_diag: Option<Box<RevertDiagnostic>>,
    pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
    pub tempo_labels: Option<Box<TempoLabels>>,
    pub tracer: Option<Box<TracingInspector>>,

    // FoundryInspectorExt and other internal data.
    /// Whether to collect sancov edge coverage from instrumented native crates.
    pub sancov_edges: bool,
    /// Whether to capture sancov trace-cmp operands for dictionary injection.
    pub sancov_trace_cmp: bool,
    pub enable_isolation: bool,
    pub networks: NetworkConfigs,
    pub create2_deployer: Address,
    /// Flag marking if we are in the inner EVM context.
    pub in_inner_context: bool,
    pub inner_context_data: Option<InnerContextData>,
    /// Accounts that should retain the per-transaction creation marker in the current context.
    pub locally_created_accounts: AddressHashSet,
    pub top_frame_journal: AddressMap<Account>,
    /// Address that reverted the call, if any.
    pub reverter: Option<Address>,
    /// LIFO stack tracking CREATE2 frames that were redirected to the CREATE2 factory.
    /// Each entry records the journal depth at which the redirect occurred.
    pub pending_create2_redirects: Vec<usize>,
    /// Pending CREATE2 deployer validation error, deferred from `frame_start` to `create` so
    /// it goes through the normal inspector lifecycle (tracing, etc.).
    pub pending_create2_error: Option<CreateOutcome>,
    /// Counter for CREATE2 salt in `--batch` CREATE rewrites.
    pub batch_create_counter: u64,
    /// Whether the one-shot `--batch` rewrite warning has already been emitted.
    pub batch_rewrite_warned: bool,
    /// Per-inspector random seed mixed into `--batch` CREATE2 salts, ensuring re-runs
    /// at identical on-chain state still produce distinct salts. Lazily initialized.
    pub batch_rewrite_process_salt: Option<u64>,
    /// Shared cancellation state for interruptible EVM execution.
    execution_cancellation: Option<EvmExecutionCancellation>,
    /// Amortizes deadline checks in the opcode hot path.
    cancellation_poll_counter: u8,
    /// Whether this inspector halted the current execution due to cancellation.
    execution_cancelled: bool,
    #[cfg(test)]
    early_exit_test_gate: Option<EarlyExitTestGate>,
    static_step_dispatch: OpcodeStepDispatch,
    has_static_step_end_inspectors: bool,
}

/// Struct keeping mutable references to both parts of [InspectorStack] and implementing
/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut].
pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
    pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
    pub inner: &'a mut InspectorStackInner,
}

impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
    fn with_nested_evm(
        &mut self,
        cheats: &mut Cheatcodes<FEN>,
        ecx: &mut FoundryContextFor<'_, FEN>,
        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
    ) -> Result<(), EVMError<DatabaseError>> {
        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
        with_cloned_context(ecx, |db, evm_env, journal_inner| {
            let mut evm =
                FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector);
            *evm.journal_inner_mut() = journal_inner;
            f(&mut *evm)?;
            let sub_inner = evm.journal_inner_mut().clone();
            let sub_evm_env = evm.to_evm_env();
            Ok((sub_evm_env, sub_inner))
        })
    }

    fn with_fresh_nested_evm(
        &mut self,
        cheats: &mut Cheatcodes<FEN>,
        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
        evm_env: EvmEnvFor<FEN>,
        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
    ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
        let mut evm =
            FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector);
        f(&mut *evm)?;
        Ok(evm.to_evm_env())
    }

    fn transact_on_db(
        &mut self,
        cheats: &mut Cheatcodes<FEN>,
        ecx: &mut FoundryContextFor<'_, FEN>,
        fork_id: Option<U256>,
        transaction: B256,
    ) -> eyre::Result<()> {
        let evm_env = ecx.evm_clone();
        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
        let (db, inner) = ecx.db_journal_inner_mut();
        db.transact(fork_id, transaction, evm_env, inner, &mut inspector)
    }

    fn transact_from_tx_on_db(
        &mut self,
        cheats: &mut Cheatcodes<FEN>,
        ecx: &mut FoundryContextFor<'_, FEN>,
        tx_env: TxEnvFor<FEN>,
    ) -> eyre::Result<()> {
        let evm_env = ecx.evm_clone();
        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
        let (db, inner) = ecx.db_journal_inner_mut();
        db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
    }

    fn console_log(&mut self, msg: &str) {
        if let Some(ref mut collector) = self.log_collector {
            InspectorExt::console_log(&mut **collector, msg);
        }
    }

    fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
        self.tracer.as_deref_mut()
    }

    fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option<Address>) {
        self.in_inner_context = enabled;
        self.inner_context_data = enabled.then(|| InnerContextData {
            original_origin: original_origin.expect("origin required when enabling inner ctx"),
            locally_created_accounts: AddressHashSet::default(),
        });
    }
}

impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
    fn default() -> Self {
        Self::new()
    }
}

impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
    /// Creates a new inspector stack.
    ///
    /// Note that the stack is empty by default, and you must add inspectors to it.
    /// This is done by calling the `set_*` methods on the stack directly, or by building the stack
    /// with [`InspectorStack`].
    #[inline]
    pub fn new() -> Self {
        Self { cheatcodes: None, inner: InspectorStackInner::default() }
    }

    /// Set the solar compiler instance.
    #[inline]
    pub fn set_analysis(&mut self, analysis: Analysis) {
        self.analysis = Some(analysis);
    }

    /// Set the cancellation state checked during EVM execution.
    #[inline]
    pub(crate) fn set_early_exit(&mut self, early_exit: EarlyExit) {
        self.execution_cancellation = Some(EvmExecutionCancellation::early_exit(early_exit));
    }

    /// Set the complete cancellation state checked during EVM execution.
    #[inline]
    pub(crate) fn set_execution_cancellation(&mut self, cancellation: EvmExecutionCancellation) {
        self.execution_cancellation = Some(cancellation);
    }

    /// Returns whether this inspector halted execution due to cancellation.
    #[inline]
    pub(crate) const fn execution_cancelled(&self) -> bool {
        self.inner.execution_cancelled
    }

    #[cfg(test)]
    pub(crate) fn set_early_exit_test_gate(
        &mut self,
        entered: std::sync::mpsc::Sender<()>,
        release: std::sync::mpsc::Receiver<()>,
        target_pc: usize,
    ) {
        self.early_exit_test_gate = Some(EarlyExitTestGate {
            entered,
            release: Arc::new(std::sync::Mutex::new(release)),
            target_pc,
            notified: Arc::new(std::sync::atomic::AtomicBool::new(false)),
        });
    }

    /// Sets the block for the relevant inspectors.
    #[inline]
    pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
        if let Some(cheatcodes) = &mut self.cheatcodes {
            cheatcodes.block = Some(block);
        }
    }

    /// Sets the gas price for the relevant inspectors.
    #[inline]
    pub fn set_gas_price(&mut self, gas_price: u128) {
        if let Some(cheatcodes) = &mut self.cheatcodes {
            cheatcodes.gas_price = Some(gas_price);
        }
    }

    /// Set the cheatcodes inspector.
    #[inline]
    pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
        self.cheatcodes = Some(cheatcodes.into());
    }

    /// Set the fuzzer inspector.
    #[inline]
    pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
        self.fuzzer = Some(fuzzer.into());
        self.refresh_static_step_dispatch();
    }

    /// Set the Chisel inspector.
    #[inline]
    pub fn set_chisel(&mut self, final_pc: usize) {
        self.chisel_state = Some(ChiselState::new(final_pc).into());
        self.refresh_static_step_end_dispatch();
    }

    /// Set whether to enable the line coverage collector.
    #[inline]
    pub fn collect_line_coverage(&mut self, yes: bool) {
        self.line_coverage = yes.then(Default::default);
        self.refresh_static_step_dispatch();
    }

    /// Set whether to enable the edge coverage collector with default config.
    #[inline]
    pub fn collect_edge_coverage(&mut self, yes: bool) {
        self.edge_coverage =
            yes.then(|| EdgeCovInspector::with_config(EdgeCovConfig::default()).into());
        self.refresh_static_step_dispatch();
    }

    /// Configure the edge coverage collector from a [`FuzzCorpusConfig`].
    ///
    /// Derives both the on/off gate and [`EdgeCovConfig`] from `corpus`.
    #[inline]
    pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
        self.edge_coverage = corpus
            .collect_evm_edge_coverage()
            .then(|| EdgeCovInspector::with_config(corpus.into()).into());
        self.refresh_static_step_dispatch();
    }

    /// Set whether to collect EVM comparison operands.
    #[inline]
    pub fn collect_evm_cmp_log(&mut self, yes: bool) {
        if yes {
            self.edge_coverage
                .get_or_insert_with(|| EdgeCovInspector::with_cmp_log_only().into())
                .enable_cmp_log(true);
        } else if let Some(edge_coverage) = &mut self.edge_coverage {
            edge_coverage.enable_cmp_log(false);
        }
        self.refresh_static_step_dispatch();
    }

    /// Set whether to collect sancov edge coverage from instrumented native crates.
    #[inline]
    pub const fn collect_sancov_edges(&mut self, yes: bool) {
        self.inner.sancov_edges = yes;
    }

    /// Set whether to capture sancov trace-cmp operands for dictionary injection.
    #[inline]
    pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
        self.inner.sancov_trace_cmp = yes;
    }

    /// Set whether to enable call isolation.
    #[inline]
    pub const fn enable_isolation(&mut self, yes: bool) {
        self.inner.enable_isolation = yes;
    }

    /// Set networks with enabled features.
    #[inline]
    pub const fn networks(&mut self, networks: NetworkConfigs) {
        self.inner.networks = networks;
    }

    /// Set the CREATE2 deployer address.
    #[inline]
    pub fn set_create2_deployer(&mut self, deployer: Address) {
        self.create2_deployer = deployer;
    }

    /// Set whether to enable the log collector.
    /// - None for no log collection.
    /// - Some(true) for realtime console.log-ing.
    /// - Some(false) for log collection.
    #[inline]
    pub fn collect_logs(&mut self, live_logs: Option<bool>) {
        self.log_collector = live_logs.map(|live_logs| {
            Box::new(if live_logs {
                LogCollector::LiveLogs
            } else {
                LogCollector::Capture { logs: Vec::new() }
            })
        });
    }

    /// Set whether to enable the trace printer.
    #[inline]
    pub fn print(&mut self, yes: bool) {
        self.printer = yes.then(Default::default);
        self.refresh_static_opcode_dispatch();
    }

    /// Set trace data requirements.
    #[inline]
    pub fn tracing_requirements(&mut self, requirements: TraceRequirements) {
        let config = requirements.into_config();
        self.revert_diag = config.is_some().then(RevertDiagnostic::default).map(Into::into);

        if let Some(config) = config {
            *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
        } else {
            self.tracer = None;
        }
        self.refresh_static_opcode_dispatch();
    }

    /// Set whether to enable script execution inspector.
    #[inline]
    pub fn script(&mut self, script_address: Address) {
        self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
            script_address;
        self.refresh_static_step_dispatch();
    }

    #[inline(always)]
    fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
        InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
    }

    /// Collects all the data gathered during inspection into a single struct.
    pub fn collect(self) -> InspectorData<FEN> {
        let Self {
            mut cheatcodes,
            inner:
                InspectorStackInner {
                    chisel_state,
                    line_coverage,
                    edge_coverage,
                    log_collector,
                    tempo_labels,
                    tracer,
                    revert_diag,
                    reverter,
                    ..
                },
        } = self;

        let trace_diagnostics =
            revert_diag.map(|revert_diag| revert_diag.into_diagnostics()).unwrap_or_default();

        let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
            let ignored = cheatcodes
                .as_mut()
                .map(|cheatcodes| {
                    let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);

                    // If the last pause call was not resumed, ignore the rest of the trace
                    if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
                        ignored.insert(last_pause_call, (arena.nodes().len(), 0));
                    }

                    ignored
                })
                .unwrap_or_default();

            SparsedTraceArena { arena, ignored, diagnostics: trace_diagnostics }
        });

        let (edge_coverage, evm_cmp_values) = edge_coverage
            .map(|edge_coverage| {
                let (hitcount, cmp_values) = edge_coverage.into_parts();
                (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
            })
            .unwrap_or_default();

        InspectorData {
            logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
            labels: {
                let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default();
                if let Some(tempo_labels) = tempo_labels {
                    labels.extend(tempo_labels.labels);
                }
                labels
            },
            traces,
            line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
            edge_coverage,
            evm_cmp_values,
            cheatcodes,
            chisel_state: chisel_state.and_then(|state| state.state),
            reverter,
        }
    }
}

impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
    /// Adjusts the EVM data for the inner EVM context.
    /// Should be called on the top-level call of inner context (depth == 0 &&
    /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility
    /// Updates tx.origin to the value before entering inner context
    fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
        let inner_context_data =
            self.inner_context_data.as_ref().expect("should be called in inner context");
        ecx.tx_mut().set_caller(inner_context_data.original_origin);
    }

    fn do_call_end(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        inputs: &CallInputs,
        outcome: &mut CallOutcome,
    ) {
        let storage_hook_active =
            self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
        if !storage_hook_active && let Some(fuzzer) = &mut self.fuzzer {
            fuzzer.call_end(ecx, inputs, outcome);
        }

        let result = outcome.result.result;
        call_inspectors!(
            #[ret]
            [&mut self.tracer, &mut self.cheatcodes, &mut self.printer, &mut self.revert_diag],
            |inspector| {
                let previous_output = outcome.output().clone();
                inspector.call_end(ecx, inputs, outcome);

                // If the inspector returns a different status or a revert with a non-empty message,
                // we assume it wants to tell us something
                let different = outcome.result.result != result
                    || (outcome.result.result == InstructionResult::Revert
                        && outcome.output() != &previous_output);
                different.then_some(())
            },
        );

        // Record first address that reverted the call.
        if result.is_revert() && self.reverter.is_none() {
            self.reverter = Some(inputs.target_address);
        }
    }

    fn do_create_end(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        call: &CreateInputs,
        outcome: &mut CreateOutcome,
    ) {
        let result = outcome.result.result;
        call_inspectors!(
            #[ret]
            [&mut self.tracer, &mut self.cheatcodes, &mut self.printer],
            |inspector| {
                let previous_output = outcome.output().clone();
                inspector.create_end(ecx, call, outcome);

                // If the inspector returns a different status or a revert with a non-empty message,
                // we assume it wants to tell us something
                let different = outcome.result.result != result
                    || (outcome.result.result == InstructionResult::Revert
                        && outcome.output() != &previous_output);
                different.then_some(())
            },
        );
    }

    fn transact_inner(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        kind: TxKind,
        caller: Address,
        input: Bytes,
        gas_limit: u64,
        value: U256,
    ) -> (InterpreterResult, Option<Address>) {
        let cached_evm_env = ecx.evm_clone();
        let cached_tx_env = ecx.tx_clone();

        ecx.block_mut().set_basefee(0);

        let chain_id = ecx.cfg().chain_id();
        ecx.tx_mut().set_chain_id(Some(chain_id));
        ecx.tx_mut().set_caller(caller);
        ecx.tx_mut().set_kind(kind);
        ecx.tx_mut().set_data(input);
        ecx.tx_mut().set_value(value);
        // Add 21000 to the gas limit to account for the base cost of transaction.
        ecx.tx_mut().set_gas_limit(gas_limit + 21000);

        // If we haven't disabled gas limit checks, ensure that transaction gas limit will not
        // exceed block gas limit.
        if !ecx.cfg().is_block_gas_limit_disabled() {
            let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
            ecx.tx_mut().set_gas_limit(gas_limit);
        }
        ecx.tx_mut().set_gas_price(0);
        // If the cached tx is EIP-4844 (e.g. set by `vm.blobhashes`), downgrade
        // it to EIP-1559 and drop the blob hashes so revm doesn't reject the
        // synthetic inner tx because `gas_price = 0` plus blob fields fail
        // 4844 validation. The contract-visible `BLOBHASH` opcode is restored
        // via `EnvOverrides`. Other types (incl. EIP-2930 access lists) are
        // intentionally left intact, and the full original tx is restored
        // from `cached_tx_env` after the inner call.
        if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
            ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
            ecx.tx_mut().set_blob_hashes(Vec::new());
        }

        let locally_created_accounts = ecx
            .journal()
            .evm_state()
            .iter()
            .filter_map(|(addr, acc)| acc.is_created_locally().then_some(*addr))
            .collect();
        self.inner_context_data = Some(InnerContextData {
            original_origin: cached_tx_env.caller(),
            locally_created_accounts,
        });
        self.in_inner_context = true;

        // Tell cheatcodes we're entering the synthetic inner transaction so
        // env-mutating cheatcodes route through `env_overrides` instead of
        // fighting with the fee-accounting zeroing above. See `EnvOverrides`.
        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
            cheats.in_isolation_context = true;
        }

        let evm_env = ecx.evm_clone();
        let tx_env = ecx.tx_clone();

        let res = self.with_inspector(|mut inspector| {
            let (res, nested_env) = {
                let (db, journal) = ecx.db_journal_inner_mut();
                let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(
                    db,
                    evm_env,
                    &mut inspector,
                );

                evm.journal_inner_mut().state = {
                    let mut state = journal.state.clone();

                    for (addr, acc_mut) in &mut state {
                        // Preserve revm's per-transaction creation flag for accounts created in
                        // the parent context in initialize_interp. A cold load in the nested
                        // context clears local flags, but keeping accounts cold preserves gas
                        // accounting for isolated calls.
                        if journal.warm_addresses.is_cold(addr) {
                            acc_mut.mark_cold();
                        }

                        // mark all slots cold
                        for slot_mut in acc_mut.storage.values_mut() {
                            slot_mut.is_cold = true;
                            slot_mut.original_value = slot_mut.present_value;
                        }
                    }

                    state
                };

                // set depth to 1 to make sure traces are collected correctly
                evm.journal_inner_mut().depth = 1;

                let res = evm.transact_raw(tx_env);
                let nested_evm_env = evm.to_evm_env();
                (res, nested_evm_env)
            };

            // Restore env, preserving cheatcode cfg/block changes from the nested EVM
            // but restoring the original tx and basefee (which we zeroed for the nested call).
            let mut restored_evm_env = nested_env;
            restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
            ecx.set_evm(restored_evm_env);
            ecx.set_tx(cached_tx_env);

            res
        });

        self.in_inner_context = false;
        self.inner_context_data = None;

        // Reset the cheatcodes isolation flag now that the synthetic inner
        // transaction has finished.
        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
            cheats.in_isolation_context = false;
        }

        let mut gas = Gas::new(gas_limit);

        let Ok(res) = res else {
            // Should we match, encode and propagate error as a revert reason?
            let result =
                InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
            return (result, None);
        };

        for (addr, mut acc) in res.state {
            let Some(acc_mut) = ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
                ecx.journal_mut().evm_state_mut().insert(addr, acc);
                continue;
            };

            // make sure accounts that were warmed earlier do not become cold
            if acc.status.contains(AccountStatus::Cold)
                && !acc_mut.status.contains(AccountStatus::Cold)
            {
                acc.status -= AccountStatus::Cold;
            }
            acc_mut.info = acc.info;
            acc_mut.status |= acc.status;

            for (key, val) in acc.storage {
                let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
                    acc_mut.storage.insert(key, val);
                    continue;
                };
                slot_mut.present_value = val.present_value;
                slot_mut.is_cold &= val.is_cold;
            }
        }

        let (result, address, output) = match res.result {
            ExecutionResult::Success { reason, gas: result_gas, logs: _, output } => {
                gas.set_refund(result_gas.final_refunded() as i64);
                let _ = gas.record_regular_cost(result_gas.tx_gas_used());
                let address = match output {
                    Output::Create(_, address) => address,
                    Output::Call(_) => None,
                };
                (reason.into(), address, output.into_data())
            }
            ExecutionResult::Halt { reason, gas: result_gas, .. } => {
                let _ = gas.record_regular_cost(result_gas.tx_gas_used());
                (InstructionResult::from(reason), None, Bytes::new())
            }
            ExecutionResult::Revert { gas: result_gas, output, .. } => {
                let _ = gas.record_regular_cost(result_gas.tx_gas_used());
                (InstructionResult::Revert, None, output)
            }
        };
        (InterpreterResult { result, output, gas }, address)
    }

    /// Moves out of references, constructs a new [`InspectorStackRefMut`] and runs the given
    /// closure with it.
    fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
        let mut cheatcodes = self
            .cheatcodes
            .as_deref_mut()
            .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone())));
        let mut inner = std::mem::take(self.inner);

        // Save pending CREATE2 redirects so frame_end in the nested EVM doesn't consume them.
        // These belong to the outer EVM's frame lifecycle and must be restored after.
        let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);

        let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });

        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
            *cheats = cheatcodes.unwrap();
        }

        inner.pending_create2_redirects = saved_create2_redirects;
        *self.inner = inner;

        out
    }

    /// Invoked at the beginning of a new top-level (0 depth) frame.
    fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
        self.locally_created_accounts.clear();
        if let Some(cheatcodes) = &mut self.cheatcodes {
            cheatcodes.clear_storage_hook_mapping_slots();
        }

        if self.enable_isolation {
            // If we're in isolation mode, we need to keep track of the state at the beginning of
            // the frame to be able to roll back on revert
            self.top_frame_journal.clone_from(ecx.journal().evm_state());
        }
    }

    /// Invoked at the end of root frame.
    fn top_level_frame_end(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        result: InstructionResult,
    ) {
        if let Some(cheatcodes) = &mut self.cheatcodes {
            cheatcodes.clear_storage_hook_mapping_slots();
        }
        if !result.is_revert() {
            return;
        }
        // Encountered a revert, since cheatcodes may have altered the evm state in such a way
        // that violates some constraints, e.g. `deal`, we need to manually roll back on revert
        // before revm reverts the state itself
        if let Some(cheats) = self.cheatcodes.as_mut() {
            cheats.on_revert(ecx);
        }

        // If we're in isolation mode, we need to rollback to state before the root frame was
        // created We can't rely on revm's journal because it doesn't account for changes
        // made by isolated calls
        if self.enable_isolation {
            *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
        }
    }

    // We take extra care in optimizing `step` and `step_end`, as they're are likely the most
    // hot functions in all of Foundry.
    // We want to `#[inline(always)]` these functions so that `InspectorStack` does not
    // delegate to `InspectorStackRefMut` in this case.

    #[inline(always)]
    fn step_inlined(
        &mut self,
        interpreter: &mut Interpreter,
        ecx: &mut FoundryContextFor<'_, FEN>,
    ) {
        let storage_hook_active = if let Some(cheats) = self.cheatcodes.as_mut() {
            if cheats.has_storage_hooks() && cheats.finish_storage_hook_callback(interpreter, ecx) {
                return;
            }
            cheats.pc = interpreter.bytecode.pc();
            cheats.is_storage_hook_active()
        } else {
            false
        };

        #[cfg(test)]
        if interpreter.bytecode.opcode() == op::JUMPDEST
            && let Some(gate) = &self.inner.early_exit_test_gate
            && interpreter.bytecode.pc() == gate.target_pc
            && !gate.notified.swap(true, std::sync::atomic::Ordering::Relaxed)
        {
            let _ = gate.entered.send(());
            let _ = gate
                .release
                .lock()
                .expect("early-exit test gate lock poisoned")
                .recv_timeout(std::time::Duration::from_secs(1));
        }

        if let Some(cancellation) = &self.inner.execution_cancellation {
            let poll_deadline = self.inner.cancellation_poll_counter == 0;
            self.inner.cancellation_poll_counter =
                self.inner.cancellation_poll_counter.wrapping_add(1);
            if cancellation.should_stop(poll_deadline) {
                self.inner.execution_cancelled = true;
                interpreter.halt(InstructionResult::Stop);
                return;
            }
        }

        match self.static_step_dispatch {
            OpcodeStepDispatch::None => {}
            OpcodeStepDispatch::FuzzerOnly => {
                if !storage_hook_active && let Some(inspector) = &mut self.fuzzer {
                    inspector.step(interpreter, ecx);
                }
            }
            OpcodeStepDispatch::General => {
                if storage_hook_active {
                    call_inspectors!(
                        [
                            // These are sorted in definition order.
                            &mut self.printer,
                            &mut self.revert_diag,
                            &mut self.script_execution_inspector,
                            &mut self.tracer,
                        ],
                        |inspector| (**inspector).step(interpreter, ecx),
                    );
                } else {
                    call_inspectors!(
                        [
                            // These are sorted in definition order.
                            &mut self.edge_coverage,
                            &mut self.fuzzer,
                            &mut self.line_coverage,
                            &mut self.printer,
                            &mut self.revert_diag,
                            &mut self.script_execution_inspector,
                            &mut self.tracer,
                        ],
                        |inspector| (**inspector).step(interpreter, ecx),
                    );
                }
            }
        }

        if let Some(cheats) = self.cheatcodes.as_mut()
            && cheats.has_step_hooks()
        {
            let opcode = interpreter.bytecode.opcode();
            if !cheats.has_recording_accesses_only_step_hook()
                || matches!(opcode, op::SLOAD | op::SSTORE)
            {
                crate::utils::cold_path();
                cheats.step(interpreter, ecx);
            }
        }
    }

    #[inline(always)]
    fn step_end_inlined(
        &mut self,
        interpreter: &mut Interpreter,
        ecx: &mut FoundryContextFor<'_, FEN>,
    ) {
        if self.has_static_step_end_inspectors {
            call_inspectors!(
                [
                    // These are sorted in definition order.
                    &mut self.chisel_state,
                    &mut self.printer,
                    &mut self.revert_diag,
                    &mut self.tracer,
                ],
                |inspector| (**inspector).step_end(interpreter, ecx),
            );
        }

        if let Some(fuzzer) = &mut self.fuzzer
            && fuzzer.mapping_slots.is_some()
        {
            fuzzer.step_end(interpreter, ecx);
        }

        if let Some(cheats) = self.cheatcodes.as_mut()
            && cheats.has_step_end_hooks()
        {
            crate::utils::cold_path();
            cheats.step_end(interpreter, ecx);
        }
    }
}

impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
    for InspectorStackRefMut<'_, FEN>
{
    fn initialize_interp(
        &mut self,
        interpreter: &mut Interpreter,
        ecx: &mut FoundryContextFor<'_, FEN>,
    ) {
        let address = interpreter.input.target_address();
        let should_mark_created_locally = self.locally_created_accounts.contains(&address)
            || self
                .inner_context_data
                .as_ref()
                .is_some_and(|ctx| ctx.locally_created_accounts.contains(&address));
        if should_mark_created_locally
            && let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&address)
        {
            account.mark_created_locally();
        }

        call_inspectors!(
            [
                &mut self.line_coverage,
                &mut self.tracer,
                &mut self.cheatcodes,
                &mut self.script_execution_inspector,
                &mut self.printer
            ],
            |inspector| inspector.initialize_interp(interpreter, ecx),
        );
    }

    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
        self.step_inlined(interpreter, ecx);
    }

    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
        self.step_end_inlined(interpreter, ecx);
    }

    #[allow(clippy::redundant_clone)]
    fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
        call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
            inspector.log(ecx, log.clone())
        });
        if let Some(inspector) = &mut self.cheatcodes
            && inspector.has_log_hooks()
        {
            crate::utils::cold_path();
            inspector.log(ecx, log.clone());
        }
        call_inspectors!([&mut self.printer], |inspector| { inspector.log(ecx, log.clone()) });
    }

    #[allow(clippy::redundant_clone)]
    fn log_full(
        &mut self,
        interpreter: &mut Interpreter,
        ecx: &mut FoundryContextFor<'_, FEN>,
        log: Log,
    ) {
        call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
            inspector.log_full(interpreter, ecx, log.clone())
        });
        if let Some(inspector) = &mut self.cheatcodes
            && inspector.has_log_hooks()
        {
            crate::utils::cold_path();
            inspector.log_full(interpreter, ecx, log.clone());
        }
        call_inspectors!([&mut self.printer], |inspector| {
            inspector.log_full(interpreter, ecx, log.clone())
        });
    }

    fn frame_start(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        frame_input: &mut FrameInput,
    ) -> Option<FrameResult> {
        if let FrameInput::Create(inputs) = frame_input
            && self.should_use_create2_factory(ecx.journal().depth(), inputs)
        {
            // `get_create2_factory_call_inputs` forwards `inputs.value()` to the factory
            // as `CallValue::Transfer`, and the default factory runtime forwards CALLVALUE
            // into CREATE2, so payable deploys are supported on both the explicit
            // `new C{salt, value}` path and the `--batch` rewrite path.
            let salt = match inputs.scheme() {
                CreateScheme::Create2 { salt } => salt,
                // --batch: process_salt (random per run) + counter make each deploy unique.
                // The nonce below is from the EVM frame caller (script contract), not the EOA.
                CreateScheme::Create => {
                    if !self.inner.batch_rewrite_warned {
                        let _ = sh_warn!(
                            "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
                             deployed addresses follow the CREATE2 formula and constructor \
                             msg.sender is the factory, not the EOA."
                        );
                        self.inner.batch_rewrite_warned = true;
                    }
                    let chain_id = ecx.cfg().chain_id();
                    let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
                    self.inner.next_batch_create_salt(chain_id, nonce)
                }
                _ => return None,
            };

            let gas_limit = inputs.gas_limit();
            let create2_deployer = self.create2_deployer();

            // Validate deployer before rewriting.
            let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
            if code_hash == KECCAK_EMPTY {
                // Store the revert so `create` can return it inside the normal inspector
                // lifecycle (avoids tracing mismatch from short-circuiting in frame_start).
                self.inner.pending_create2_error = Some(CreateOutcome {
                    result: InterpreterResult {
                        result: InstructionResult::Revert,
                        output: Bytes::from(
                            format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
                        ),
                        gas: Gas::new(gas_limit),
                    },
                    address: None,
                });
                return None;
            } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
                self.inner.pending_create2_error = Some(CreateOutcome {
                    result: InterpreterResult {
                        result: InstructionResult::Revert,
                        output: "invalid CREATE2 deployer bytecode".into(),
                        gas: Gas::new(gas_limit),
                    },
                    address: None,
                });
                return None;
            }

            let call_inputs =
                get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
                    .ok()?;

            // Record the redirect depth *after* validation succeeds.
            self.inner.pending_create2_redirects.push(ecx.journal().depth());

            // Rewrite the frame input from Create to Call.
            *frame_input = FrameInput::Call(Box::new(call_inputs));
        }

        None
    }

    fn frame_end(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        _frame_input: &FrameInput,
        frame_result: &mut FrameResult,
    ) {
        let depth = ecx.journal().depth();
        if self.inner.pending_create2_redirects.last().copied() != Some(depth) {
            return;
        }

        self.inner.pending_create2_redirects.pop();

        let FrameResult::Call(call) = frame_result else {
            debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
            return;
        };

        let address = match call.instruction_result() {
            return_ok!() => Address::try_from(call.output().as_ref())
                .map_err(|_| {
                    call.result = InterpreterResult {
                        result: InstructionResult::Revert,
                        output: "invalid CREATE2 factory output".into(),
                        gas: Gas::new(call.result.gas.limit()),
                    };
                })
                .ok(),
            _ => None,
        };

        *frame_result = FrameResult::Create(CreateOutcome { result: call.result.clone(), address });
    }

    fn call(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        call: &mut CallInputs,
    ) -> Option<CallOutcome> {
        if self.in_inner_context && ecx.journal().depth() == 1 {
            self.adjust_evm_data_for_inner_context(ecx);
            return None;
        }

        if ecx.journal().depth() == 0 {
            self.top_level_frame_start(ecx);
        }

        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
            revert_diag.frame_start();
        }

        let storage_hook_callback = self
            .cheatcodes
            .as_deref()
            .is_some_and(|cheatcodes| cheatcodes.is_storage_hook_callback(ecx, call));
        let storage_hook_active =
            self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);

        if !storage_hook_active {
            call_inspectors!(
                #[ret]
                [&mut self.fuzzer],
                |inspector| {
                    let mut out = None;
                    if let Some(output) = inspector.call(ecx, call) {
                        out = Some(Some(output));
                    }
                    out
                }
            );
        }

        if self.tracer.is_some() {
            crate::utils::cold_path();
            let (output, trace_idx) = {
                let tracer = self.tracer.as_deref_mut().unwrap();
                let output = tracer.call(ecx, call);
                (output, tracer.traces().nodes().len() - 1)
            };
            if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
                revert_diag.set_trace_node(trace_idx);
            }
            if output.is_some() {
                return output;
            }
        }

        call_inspectors!(
            #[ret]
            [
                &mut self.log_collector,
                &mut self.printer,
                &mut self.revert_diag,
                &mut self.tempo_labels
            ],
            |inspector| inspector.call(ecx, call).map(Some),
        );

        // Storage hook callbacks are instrumentation frames, not user calls. Let revm execute the
        // callback after tracing it, but do not apply mocks, pranks, broadcasts, or isolation.
        if storage_hook_callback {
            return None;
        }

        // The tracer records call inputs before cheatcodes apply caller overrides such as pranks
        // and broadcasts. Keep the trace lifecycle ordering, but remember the node so its caller
        // can be synchronized with the inputs that are actually executed.
        let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
        let mut cheatcode_outcome = None;
        if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
            // Handle mocked functions, replace bytecode address with mock if matched.
            if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) {
                let input_bytes = call.input.bytes(ecx);
                // Check if any mock function set for call data or if catch-all mock function set
                // for selector.
                if let Some(target) = mocks
                    .get(&input_bytes)
                    .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
                {
                    call.bytecode_address = *target;

                    let target = ecx
                        .journal_mut()
                        .load_account_with_code(*target)
                        .expect("failed to load account");
                    call.known_bytecode =
                        (target.info.code_hash, target.info.code.clone().unwrap_or_default());
                }
            }

            cheatcode_outcome = cheatcodes.call_with_executor(ecx, call, self.inner);
        }

        if let Some(trace_idx) = trace_idx
            && let Some(tracer) = self.tracer.as_deref_mut()
        {
            let caller = match call.scheme {
                CallScheme::DelegateCall | CallScheme::CallCode => call.target_address,
                CallScheme::Call | CallScheme::StaticCall => call.caller,
            };
            let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
            debug_assert_eq!(node.trace.depth, ecx.journal().depth());
            node.trace.caller = caller;
        }

        if let Some(output) = cheatcode_outcome {
            return Some(output);
        }

        if let Some(outcome) = handle_arbitrum_system_call::<FEN>(ecx, call) {
            return Some(outcome);
        }

        if self.enable_isolation && !self.in_inner_context && ecx.journal().depth() == 1 {
            match call.scheme {
                // Isolate CALLs
                CallScheme::Call => {
                    let input = call.input.bytes(ecx);
                    let (result, _) = self.transact_inner(
                        ecx,
                        TxKind::Call(call.target_address),
                        call.caller,
                        input,
                        call.gas_limit,
                        call.value.get(),
                    );
                    return Some(CallOutcome {
                        result,
                        memory_offset: call.return_memory_offset.clone(),
                        was_precompile_called: true,
                        precompile_call_logs: vec![],
                        charged_new_account_state_gas: false,
                    });
                }
                // Mark accounts and storage cold before STATICCALLs
                CallScheme::StaticCall => {
                    let (_, journal_inner) = ecx.db_journal_inner_mut();
                    let JournaledState { state, warm_addresses, .. } = journal_inner;
                    for (addr, acc_mut) in state {
                        // Do not mark accounts and storage cold accounts with arbitrary storage.
                        if let Some(cheatcodes) = &self.cheatcodes
                            && cheatcodes.has_arbitrary_storage(addr)
                        {
                            continue;
                        }

                        if warm_addresses.is_cold(addr) {
                            acc_mut.mark_cold();
                        }

                        for slot_mut in acc_mut.storage.values_mut() {
                            slot_mut.is_cold = true;
                        }
                    }
                }
                // Process other variants as usual
                CallScheme::CallCode | CallScheme::DelegateCall => {}
            }
        }

        None
    }

    fn call_end(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        inputs: &CallInputs,
        outcome: &mut CallOutcome,
    ) {
        // We are processing inner context outputs in the outer context, so need to avoid processing
        // twice.
        if self.in_inner_context && ecx.journal().depth() == 1 {
            return;
        }

        self.do_call_end(ecx, inputs, outcome);

        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
            revert_diag.frame_end();
        }

        if ecx.journal().depth() == 0 {
            self.top_level_frame_end(ecx, outcome.result.result);
        }
    }

    fn create(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        create: &mut CreateInputs,
    ) -> Option<CreateOutcome> {
        if self.in_inner_context && ecx.journal().depth() == 1 {
            self.adjust_evm_data_for_inner_context(ecx);
            return None;
        }

        if ecx.journal().depth() == 0 {
            self.top_level_frame_start(ecx);
        }

        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
            revert_diag.frame_start();
        }

        if self.tracer.is_some() {
            crate::utils::cold_path();
            let (output, trace_idx) = {
                let tracer = self.tracer.as_deref_mut().unwrap();
                let output = tracer.create(ecx, create);
                (output, tracer.traces().nodes().len() - 1)
            };
            if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
                revert_diag.set_trace_node(trace_idx);
            }
            if output.is_some() {
                return output;
            }
        }

        call_inspectors!(
            #[ret]
            [&mut self.line_coverage],
            |inspector| inspector.create(ecx, create).map(Some),
        );

        let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
        let mut cheatcode_outcome = None;
        if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
            cheatcode_outcome = cheatcodes.create(ecx, create);
        }

        if let Some(trace_idx) = trace_idx
            && let Some(tracer) = self.tracer.as_deref_mut()
        {
            let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
            debug_assert_eq!(node.trace.depth, ecx.journal().depth());
            node.trace.caller = create.caller();
        }

        if let Some(output) = cheatcode_outcome {
            return Some(output);
        }

        // If frame_start detected an invalid CREATE2 deployer, return the error here
        // (after sub-inspectors have been notified) so tracing stays balanced.
        if let Some(error) = self.inner.pending_create2_error.take() {
            return Some(error);
        }

        if !matches!(create.scheme(), CreateScheme::Create2 { .. })
            && self.enable_isolation
            && !self.in_inner_context
            && ecx.journal().depth() == 1
        {
            // In isolation mode, transact_inner returns None for the address on revert; pre-compute
            // the would-be deployed address so create_end can enforce expected_revert reverter
            // checks.
            let precomputed_address = ecx
                .journal()
                .evm_state()
                .get(&create.caller())
                .map(|acc| create.caller().create(acc.info.nonce));

            let (result, address) = self.transact_inner(
                ecx,
                TxKind::Create,
                create.caller(),
                create.init_code().clone(),
                create.gas_limit(),
                create.value(),
            );
            let address =
                address.or_else(|| if result.is_revert() { precomputed_address } else { None });
            return Some(CreateOutcome { result, address });
        }

        None
    }

    fn create_end(
        &mut self,
        ecx: &mut FoundryContextFor<'_, FEN>,
        call: &CreateInputs,
        outcome: &mut CreateOutcome,
    ) {
        if outcome.result.result.is_ok()
            && let Some(address) = outcome.address
        {
            self.locally_created_accounts.insert(address);

            if self.in_inner_context
                && let Some(inner_context) = &mut self.inner_context_data
            {
                inner_context.locally_created_accounts.insert(address);
            }
        }

        // We are processing inner context outputs in the outer context, so need to avoid processing
        // twice.
        if self.in_inner_context && ecx.journal().depth() == 1 {
            return;
        }

        self.do_create_end(ecx, call, outcome);

        if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
            revert_diag.frame_end();
        }

        if ecx.journal().depth() == 0 {
            self.top_level_frame_end(ecx, outcome.result.result);
        }
    }

    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
        call_inspectors!([&mut self.printer], |inspector| {
            Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
                inspector, contract, target, value,
            )
        });
    }
}

fn handle_arbitrum_system_call<FEN: FoundryEvmNetwork>(
    ecx: &mut FoundryContextFor<'_, FEN>,
    call: &CallInputs,
) -> Option<CallOutcome> {
    if call.target_address != arbitrum::ARB_SYS_ADDRESS
        || call.bytecode_address != arbitrum::ARB_SYS_ADDRESS
        || !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id)
    {
        return None;
    }

    let input = call.input.bytes(ecx);
    if input.get(..4) != Some(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR) {
        return None;
    }

    let block_number = ecx.db().active_fork_block_number()?;
    let Some((gas_cost, output)) = arbitrum::arb_block_number_call(call.gas_limit, block_number)
    else {
        return Some(arbitrum_call_outcome(
            call,
            InstructionResult::PrecompileOOG,
            0,
            Bytes::new(),
        ));
    };

    Some(arbitrum_call_outcome(call, InstructionResult::Return, gas_cost, output))
}

fn arbitrum_call_outcome(
    call: &CallInputs,
    result: InstructionResult,
    gas_used: u64,
    output: Bytes,
) -> CallOutcome {
    let mut gas = Gas::new(call.gas_limit);
    if result.is_ok() {
        let _ = gas.record_regular_cost(gas_used);
    } else {
        gas.spend_all();
    }

    CallOutcome {
        result: InterpreterResult { result, output, gas },
        memory_offset: call.return_memory_offset.clone(),
        was_precompile_called: true,
        precompile_call_logs: vec![],
        charged_new_account_state_gas: call.charged_new_account_state_gas,
    }
}

impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
        call_inspectors!(
            #[ret]
            [&mut self.cheatcodes],
            |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
        );

        false
    }

    fn console_log(&mut self, msg: &str) {
        call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
            inspector, msg
        ));
    }

    fn get_networks(&self) -> NetworkConfigs {
        self.inner.networks
    }

    fn create2_deployer(&self) -> Address {
        self.inner.create2_deployer
    }
}

impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
        self.as_mut().step_inlined(interpreter, ecx)
    }

    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
        self.as_mut().step_end_inlined(interpreter, ecx)
    }

    fn call(
        &mut self,
        context: &mut FoundryContextFor<'_, FEN>,
        inputs: &mut CallInputs,
    ) -> Option<CallOutcome> {
        self.as_mut().call(context, inputs)
    }

    fn call_end(
        &mut self,
        context: &mut FoundryContextFor<'_, FEN>,
        inputs: &CallInputs,
        outcome: &mut CallOutcome,
    ) {
        self.as_mut().call_end(context, inputs, outcome)
    }

    fn create(
        &mut self,
        context: &mut FoundryContextFor<'_, FEN>,
        create: &mut CreateInputs,
    ) -> Option<CreateOutcome> {
        self.as_mut().create(context, create)
    }

    fn create_end(
        &mut self,
        context: &mut FoundryContextFor<'_, FEN>,
        call: &CreateInputs,
        outcome: &mut CreateOutcome,
    ) {
        self.as_mut().create_end(context, call, outcome)
    }

    fn initialize_interp(
        &mut self,
        interpreter: &mut Interpreter,
        ecx: &mut FoundryContextFor<'_, FEN>,
    ) {
        self.as_mut().initialize_interp(interpreter, ecx)
    }

    fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
        self.as_mut().log(ecx, log)
    }

    fn log_full(
        &mut self,
        interpreter: &mut Interpreter,
        ecx: &mut FoundryContextFor<'_, FEN>,
        log: Log,
    ) {
        self.as_mut().log_full(interpreter, ecx, log)
    }

    fn frame_start(
        &mut self,
        context: &mut FoundryContextFor<'_, FEN>,
        frame_input: &mut FrameInput,
    ) -> Option<FrameResult> {
        self.as_mut().frame_start(context, frame_input)
    }

    fn frame_end(
        &mut self,
        context: &mut FoundryContextFor<'_, FEN>,
        frame_input: &FrameInput,
        frame_result: &mut FrameResult,
    ) {
        self.as_mut().frame_end(context, frame_input, frame_result)
    }

    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
        call_inspectors!([&mut self.inner.printer], |inspector| {
            Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
                inspector, contract, target, value,
            )
        });
    }
}

impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
        self.as_mut().should_use_create2_factory(depth, inputs)
    }

    fn get_networks(&self) -> NetworkConfigs {
        self.networks
    }

    fn create2_deployer(&self) -> Address {
        self.create2_deployer
    }
}

impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
    type Target = &'a mut InspectorStackInner;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
    type Target = InspectorStackInner;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl InspectorStackInner {
    #[inline]
    const fn refresh_static_opcode_dispatch(&mut self) {
        self.refresh_static_step_dispatch();
        self.refresh_static_step_end_dispatch();
    }

    #[inline]
    const fn refresh_static_step_dispatch(&mut self) {
        self.static_step_dispatch = if self.edge_coverage.is_none()
            && self.line_coverage.is_none()
            && self.printer.is_none()
            && self.revert_diag.is_none()
            && self.script_execution_inspector.is_none()
            && self.tracer.is_none()
        {
            if self.fuzzer.is_some() {
                OpcodeStepDispatch::FuzzerOnly
            } else {
                OpcodeStepDispatch::None
            }
        } else {
            OpcodeStepDispatch::General
        };
    }

    #[inline]
    const fn refresh_static_step_end_dispatch(&mut self) {
        self.has_static_step_end_inspectors = self.chisel_state.is_some()
            || self.printer.is_some()
            || self.revert_diag.is_some()
            || self.tracer.is_some();
    }

    /// Derive the next `--batch` CREATE2 salt and advance the per-batch counter.
    /// The per-inspector random seed is lazily initialized on first use.
    fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
        let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
        let counter = self.batch_create_counter;
        self.batch_create_counter = counter.wrapping_add(1);
        compute_batch_create_salt(process_salt, chain_id, nonce, counter)
    }
}

/// Derive the CREATE2 salt used by `--batch` CREATE rewrites.
///
/// Mixes a per-inspector random seed, the chain id, the caller nonce, and a per-batch counter
/// so that two simulations launched at the same on-chain state produce distinct salts and
/// do not collide at the Arachnid factory.
fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
    let mut buf = [0u8; 32];
    buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
    buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
    buf[16..24].copy_from_slice(&nonce.to_be_bytes());
    buf[24..32].copy_from_slice(&counter.to_be_bytes());
    U256::from_be_bytes(keccak256(buf).0)
}

#[cfg(test)]
mod tests {
    use super::{
        Address, Fuzzer, InspectorStack, InspectorStackInner, OpcodeStepDispatch, RevertDiagnostic,
        TraceRequirements, compute_batch_create_salt,
    };
    use foundry_evm_core::evm::EthEvmNetwork;

    #[test]
    fn opcode_dispatch_defaults_to_no_static_inspectors() {
        let stack = InspectorStackInner::default();

        assert_eq!(stack.static_step_dispatch, OpcodeStepDispatch::None);
        assert!(!stack.has_static_step_end_inspectors);
    }

    #[test]
    fn opcode_dispatch_uses_fuzzer_fast_path_when_fuzzer_is_only_static_step_inspector() {
        let mut stack = InspectorStack::<EthEvmNetwork>::new();
        stack.set_fuzzer(Fuzzer::new(16, None));

        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
        assert!(!stack.inner.has_static_step_end_inspectors);

        stack.collect_line_coverage(true);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);

        stack.collect_line_coverage(false);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
    }

    #[test]
    fn opcode_dispatch_tracks_general_step_and_step_end_inspectors() {
        let mut stack = InspectorStack::<EthEvmNetwork>::new();

        stack.print(true);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
        assert!(stack.inner.has_static_step_end_inspectors);

        stack.print(false);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
        assert!(!stack.inner.has_static_step_end_inspectors);

        stack.tracing_requirements(TraceRequirements::none().with_calls(true));
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
        assert!(stack.inner.has_static_step_end_inspectors);

        stack.tracing_requirements(TraceRequirements::none());
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
        assert!(!stack.inner.has_static_step_end_inspectors);

        stack.set_chisel(0);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
        assert!(stack.inner.has_static_step_end_inspectors);
    }

    #[test]
    fn opcode_dispatch_tracks_script_and_edge_coverage_inspectors() {
        let mut stack = InspectorStack::<EthEvmNetwork>::new();

        stack.script(Address::with_last_byte(1));
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
        assert!(!stack.inner.has_static_step_end_inspectors);

        let mut stack = InspectorStack::<EthEvmNetwork>::new();
        stack.collect_edge_coverage(true);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
        stack.collect_edge_coverage(false);
        assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
    }

    #[test]
    fn revert_diagnostic_frames_remain_balanced() {
        let mut inspector = RevertDiagnostic::default();
        inspector.frame_start();
        inspector.set_trace_node(0);
        inspector.frame_start();
        inspector.set_trace_node(1);
        inspector.frame_end();
        inspector.frame_end();

        assert!(inspector.into_diagnostics().is_empty());
    }

    #[test]
    fn distinct_salts_across_simulations_at_same_nonce() {
        // Two "simulations" with different per-inspector seeds but identical on-chain state.
        let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
        let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
        assert_ne!(a, b);
    }

    #[test]
    fn counter_changes_salt() {
        let a = compute_batch_create_salt(1, 1, 5, 0);
        let b = compute_batch_create_salt(1, 1, 5, 1);
        assert_ne!(a, b);
    }

    #[test]
    fn chain_id_changes_salt() {
        let a = compute_batch_create_salt(1, 1, 5, 0);
        let b = compute_batch_create_salt(1, 2, 5, 0);
        assert_ne!(a, b);
    }

    #[test]
    fn deterministic_for_same_inputs() {
        let a = compute_batch_create_salt(42, 1, 5, 7);
        let b = compute_batch_create_salt(42, 1, 5, 7);
        assert_eq!(a, b);
    }

    #[test]
    fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
        // Two fresh inspectors fed identical (chain_id, nonce) must produce CREATE2 addresses
        // that differ when routed through the same factory with the same init code.
        let factory = Address::with_last_byte(0x42);
        let init_code = b"\x60\x80\x60\x40".as_slice();

        let mut a = InspectorStackInner::default();
        let mut b = InspectorStackInner::default();
        let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
        let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();

        let addr_a = factory.create2_from_code(salt_a, init_code);
        let addr_b = factory.create2_from_code(salt_b, init_code);
        assert_ne!(addr_a, addr_b);
    }
}
