From bddfa6f6d8b45c197167a95a61f32fca198559b8 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 29 May 2026 03:09:37 +0200 Subject: [PATCH 01/17] feat(inspector): configure stepped opcodes --- AGENTS.md | 6 + crates/evm2/src/evm/config.rs | 92 +++++-- crates/evm2/src/evm/inspector.rs | 236 +++++++++++++++++- crates/evm2/src/evm/mod.rs | 27 +- crates/evm2/src/interpreter/dispatch/mod.rs | 17 +- .../src/interpreter/dispatch/table/mod.rs | 34 +-- .../src/interpreter/dispatch/table/packed.rs | 5 + crates/evm2/src/interpreter/runtime.rs | 6 +- 8 files changed, 376 insertions(+), 47 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 84111486..1af7277f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,12 @@ This is a work-in-progress repo with no public API stability guarantees. Do not backwards-compatibility aliases, deprecated wrappers, compatibility shims, or similar transitional API layers unless explicitly requested. +## Code Style + +- for public structs that should be non-exhaustive, prefer the repo pattern: + a hidden public `_non_exhaustive: ()` field initialized by constructors, not + `#[non_exhaustive]`, unless the surrounding code already uses the attribute. + ## Commands ```bash diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index 39875a8e..07ad5558 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -3,12 +3,14 @@ use crate::{ OpcodeConfig, SpecId, ethereum::RecoveredTxEnvelope, + evm::inspector::InspectorConfig, interpreter::{ Host, - dispatch::{ConfigInstrTables, InstrTable, SelectorInstrTables}, + dispatch::{self, ConfigInstrTables, InstrTable, SelectorInstrTables}, }, version::Version, }; +use alloc::boxed::Box; use derive_where::derive_where; /// Runtime EVM type family. @@ -117,59 +119,80 @@ where /// an EVM instance. This is the data passed to the interpreter when it runs. #[derive_where(Debug)] pub struct ExecutionConfig { - pub(crate) version: Version, + inner: Box>, +} + +#[derive_where(Debug)] +struct ExecutionConfigInner { + version: Version, #[derive_where(skip)] - pub(crate) instructions: &'static InstrTable, + instructions: &'static InstrTable, #[derive_where(skip)] - pub(crate) inspect_instructions: &'static InstrTable, + inspect_instructions: InstrTable, + #[derive_where(skip)] + inspect_instruction_source: &'static InstrTable, } impl Clone for ExecutionConfig { #[inline] fn clone(&self) -> Self { - *self + Self { inner: self.inner.clone() } } } -impl Copy for ExecutionConfig {} +impl Clone for ExecutionConfigInner { + #[inline] + fn clone(&self) -> Self { + Self { + version: self.version, + instructions: self.instructions, + inspect_instructions: self.inspect_instructions, + inspect_instruction_source: self.inspect_instruction_source, + } + } +} impl ExecutionConfig { /// Creates an execution config for a base `SpecId` through selector `F`. /// /// This uses the selector's base inherited tables by passing `u32::MAX` as the custom-spec /// sentinel. - #[inline] - pub(crate) const fn for_base_spec>(base_spec_id: SpecId) -> Self { + pub(crate) fn for_base_spec>(base_spec_id: SpecId) -> Self { Self::for_custom_spec::(base_spec_id) } /// Creates an execution config for selector custom spec `CUSTOM_SPEC_ID` and base `SpecId`. - #[inline] - pub(crate) const fn for_custom_spec, const CUSTOM_SPEC_ID: u32>( + pub(crate) fn for_custom_spec, const CUSTOM_SPEC_ID: u32>( base_spec_id: SpecId, ) -> Self { let i = base_spec_id as usize; + let inspect_instruction_source = + &SelectorInstrTables::::INSPECT_INSTRUCTIONS[i]; Self { - version: Version::new(base_spec_id), - instructions: &SelectorInstrTables::::INSTRUCTIONS[i], - inspect_instructions: - &SelectorInstrTables::::INSPECT_INSTRUCTIONS[i], + inner: Box::new(ExecutionConfigInner { + version: Version::new(base_spec_id), + instructions: &SelectorInstrTables::::INSTRUCTIONS[i], + inspect_instructions: *inspect_instruction_source, + inspect_instruction_source, + }), } } /// Creates an execution config for concrete EVM configuration `C`. - #[inline] - pub const fn for_config>() -> Self { + pub fn for_config>() -> Self { let base_spec_id = C::BASE_SPEC_ID; + let inspect_instruction_source = ConfigInstrTables::::INSPECT_INSTRUCTIONS; Self { - version: Version::new(base_spec_id), - instructions: ConfigInstrTables::::INSTRUCTIONS, - inspect_instructions: ConfigInstrTables::::INSPECT_INSTRUCTIONS, + inner: Box::new(ExecutionConfigInner { + version: Version::new(base_spec_id), + instructions: ConfigInstrTables::::INSTRUCTIONS, + inspect_instructions: *inspect_instruction_source, + inspect_instruction_source, + }), } } /// Creates an execution config for `spec_id` with dynamic runtime version data. - #[inline] pub fn for_spec_and_version(spec_id: T::SpecId, version: Version) -> Self { let config = >::execution_config(spec_id); assert_eq!(spec_id.into(), version.spec_id, "execution config version spec mismatch"); @@ -179,15 +202,36 @@ impl ExecutionConfig { /// Replaces the runtime version data while keeping the same dispatch table. #[inline] pub fn with_version(mut self, version: Version) -> Self { - assert_eq!(self.version.spec_id, version.spec_id, "execution config version spec mismatch"); - self.version = version; + assert_eq!( + self.inner.version.spec_id, version.spec_id, + "execution config version spec mismatch" + ); + self.inner.version = version; self } /// Returns the active EVM version. #[inline] - pub const fn version(&self) -> &Version { - &self.version + pub fn version(&self) -> &Version { + &self.inner.version + } + + #[inline] + pub(crate) fn instructions(&self) -> &InstrTable { + self.inner.instructions + } + + #[inline] + pub(crate) fn inspect_instructions(&self) -> &InstrTable { + &self.inner.inspect_instructions + } + + pub(crate) fn register_inspector(&mut self, inspector_config: &InspectorConfig) { + self.inner.inspect_instructions = dispatch::make_inspect_table( + self.inner.instructions, + self.inner.inspect_instruction_source, + &inspector_config.set, + ); } } diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index e9984b87..bfbbddd3 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -7,8 +7,142 @@ use crate::{ use alloy_primitives::{Address, Log, U256}; use core::any::Any; +/// Set of opcodes an inspector wants step hooks for. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct OpcodeSet(U256); + +impl OpcodeSet { + /// Empty opcode set. + pub const EMPTY: Self = Self(U256::ZERO); + + /// Set containing every opcode. + pub const ALL: Self = Self(U256::MAX); + + /// Creates an opcode set from raw bits. + #[inline] + pub const fn new(bits: U256) -> Self { + Self(bits) + } + + /// Returns the raw opcode set bits. + #[inline] + pub const fn get(&self) -> U256 { + self.0 + } + + /// Returns an iterator over enabled opcodes. + #[inline] + pub const fn bits(&self) -> OpcodeSetBits { + OpcodeSetBits { bits: self.0 } + } + + /// Returns whether this set contains `opcode`. + #[inline] + pub const fn contains(&self, opcode: u8) -> bool { + self.0.bit(opcode as usize) + } + + /// Inserts `opcode` into this set. + #[inline] + pub const fn insert(&mut self, opcode: u8) { + self.0.set_bit(opcode as usize, true); + } + + /// Returns whether all opcodes in `other` are also in this set. + #[inline] + pub fn contains_set(&self, other: &Self) -> bool { + self.intersection(other).get() == other.get() + } + + /// Returns whether this set and `other` share any opcodes. + #[inline] + pub fn intersects(&self, other: &Self) -> bool { + !self.intersection(other).is_empty() + } + + /// Returns whether this set contains no opcodes. + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_zero() + } + + /// Removes `opcode` from this set. + #[inline] + pub const fn remove(&mut self, opcode: u8) { + self.0.set_bit(opcode as usize, false); + } + + /// Returns the union of this set and `other`. + #[inline] + pub fn union(&self, other: &Self) -> Self { + Self(self.0 | other.0) + } + + /// Returns the intersection of this set and `other`. + #[inline] + pub fn intersection(&self, other: &Self) -> Self { + Self(self.0 & other.0) + } + + /// Returns opcodes present in this set but not in `other`. + #[inline] + pub fn difference(&self, other: &Self) -> Self { + Self(self.0 & !other.0) + } + + /// Returns opcodes present in exactly one of the two sets. + #[inline] + pub fn symmetric_difference(&self, other: &Self) -> Self { + Self(self.0 ^ other.0) + } +} + +/// Iterator over enabled opcodes in an [`OpcodeSet`]. +#[derive(Clone, Copy, Debug)] +pub struct OpcodeSetBits { + bits: U256, +} + +impl Iterator for OpcodeSetBits { + type Item = u8; + + #[inline] + fn next(&mut self) -> Option { + if self.bits.is_zero() { + return None; + } + let bit = self.bits.trailing_zeros(); + self.bits.set_bit(bit, false); + Some(bit as u8) + } +} + +/// Execution inspection configuration. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InspectorConfig { + /// Set of opcodes for which step hooks are enabled. + pub set: OpcodeSet, + #[doc(hidden)] // Not public API. Please use an existing constructor. + pub _non_exhaustive: (), +} + +impl InspectorConfig { + /// Creates an inspector configuration. + #[inline] + pub const fn new(set: OpcodeSet) -> Self { + Self { set, _non_exhaustive: () } + } +} + /// EVM execution inspector. pub trait Inspector: Any + Send { + /// Returns this inspector's execution configuration. + #[inline] + fn config(&self) -> InspectorConfig { + InspectorConfig::new(OpcodeSet::ALL) + } + /// Called after a frame interpreter has been initialized. #[inline] fn initialize_interp(&mut self, interp: &mut Interpreter<'_, T>) { @@ -88,9 +222,9 @@ impl core::ops::DerefMut for dyn Inspector + '_ { #[cfg(test)] mod tests { - use super::Inspector; + use super::{Inspector, InspectorConfig, OpcodeSet}; use crate::{ - BaseEvmConfigSelector, BaseEvmTypes, Evm, ExecutionConfig, Precompiles, SpecId, + BaseEvmConfigSelector, BaseEvmTypes, Evm, EvmTypes, ExecutionConfig, Precompiles, SpecId, bytecode::Bytecode, constants::CALL_DEPTH_LIMIT, env::{BlockEnv, TxEnv}, @@ -106,6 +240,7 @@ mod tests { use alloc::vec::Vec; use alloy_consensus::{TxLegacy, transaction::Recovered}; use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; + use core::marker::PhantomData; #[derive(Default)] struct StepInspector { @@ -163,6 +298,36 @@ mod tests { } } + struct OpcodeInterestInspector { + steps: usize, + step_ends: usize, + opcodes: Vec, + _marker: PhantomData T>, + } + + impl Default for OpcodeInterestInspector { + fn default() -> Self { + Self { steps: 0, step_ends: 0, opcodes: Vec::new(), _marker: PhantomData } + } + } + + impl Inspector for OpcodeInterestInspector { + fn config(&self) -> InspectorConfig { + let mut set = OpcodeSet::EMPTY; + set.insert(op::ADD); + InspectorConfig::new(set) + } + + fn step(&mut self, interp: &mut Interpreter<'_, T>) { + self.steps += 1; + self.opcodes.push(interp.opcode()); + } + + fn step_end(&mut self, _interp: &mut Interpreter<'_, T>) { + self.step_ends += 1; + } + } + #[derive(Default)] struct MessageInspector { call_depth: Option, @@ -403,7 +568,8 @@ mod tests { let mut message = message.clone(); message.gas_limit = gas_limit; let mut inner = Interpreter::::new(bytecode, &tx_env, &message, false); - let config = ExecutionConfig::for_base_spec::(SpecId::OSAKA); + let mut config = ExecutionConfig::for_base_spec::(SpecId::OSAKA); + config.register_inspector(&inspector.config()); let stop = inner.run_inspect(&config, host, inspector); let stack = inner.stack().to_vec(); (stop, stack) @@ -489,6 +655,26 @@ mod tests { assert_eq!(inspector.step_ends, 1); } + #[test] + fn inspector_only_steps_interested_opcodes() { + let mut host = TestHost::default(); + let mut inspector = OpcodeInterestInspector::default(); + + let (stop, stack) = run_with_inspector( + Vec::from([op::PUSH1, 1, op::PUSH1, 2, op::ADD, op::STOP]), + &mut host, + &Message::default(), + 10_000, + &mut inspector, + ); + + assert_eq!(stop, InstrStop::Stop); + assert_eq!(stack, [Word::from(3)]); + assert_eq!(inspector.steps, 1); + assert_eq!(inspector.step_ends, 1); + assert_eq!(inspector.opcodes, [op::ADD]); + } + #[test] fn call_too_deep_is_inspected_without_host_call() { let target = Address::from([0x22; 20]); @@ -823,6 +1009,50 @@ mod tests { assert_eq!(state.creates, 0); } + #[test] + fn evm_transaction_registers_inspector_opcode_interest() { + let caller = Address::from([0xaa; 20]); + let contract = Address::from([0xbb; 20]); + let code = Bytecode::new_legacy(Bytes::from_static(&[ + op::PUSH1, + 1, + op::PUSH1, + 2, + op::ADD, + op::STOP, + ])); + let mut database = InMemoryDB::default(); + database.insert_account_info( + &caller, + AccountInfo::default().with_balance(U256::from(1_000_000_000_u64)), + ); + database.insert_account_info(&contract, AccountInfo::default().with_code(code)); + let mut evm = Evm::::new( + SpecId::OSAKA, + BlockEnv::default(), + ethereum_tx_registry(SpecId::OSAKA), + database, + Precompiles::base(SpecId::OSAKA), + ); + evm.set_inspector(OpcodeInterestInspector::::default()); + let tx = RecoveredTxEnvelope::Legacy(Recovered::new_unchecked( + TxLegacy { to: TxKind::Call(contract), gas_limit: 100_000, ..Default::default() }, + caller, + )); + + let result = evm.transact(&tx).unwrap(); + let inspector = evm + .inspector() + .unwrap() + .downcast_ref::>() + .unwrap(); + + assert!(result.status); + assert_eq!(inspector.steps, 1); + assert_eq!(inspector.step_ends, 1); + assert_eq!(inspector.opcodes, [op::ADD]); + } + #[test] fn evm_transaction_inspects_eip7708_transfer_log() { let caller = Address::from([0xaa; 20]); diff --git a/crates/evm2/src/evm/mod.rs b/crates/evm2/src/evm/mod.rs index 6f66d66e..13237f19 100644 --- a/crates/evm2/src/evm/mod.rs +++ b/crates/evm2/src/evm/mod.rs @@ -1,7 +1,7 @@ //! EVM execution host. use self::{ - inspector::Inspector, + inspector::{Inspector, InspectorConfig}, precompile::{PrecompileOutput, PrecompileProvider}, }; use crate::{ @@ -65,6 +65,7 @@ pub struct Evm { interpreter_pool: InterpreterPool, #[derive_where(skip)] inspector: Option>>, + registered_inspector_config: Option, #[cfg(feature = "async")] #[derive_where(skip)] async_stack: crate::async_::FiberStack, @@ -136,6 +137,7 @@ impl Evm { precompiles, interpreter_pool: InterpreterPool::new(), inspector: None, + registered_inspector_config: None, #[cfg(feature = "async")] async_stack: crate::async_::FiberStack::default(), db_error_code: None, @@ -259,6 +261,7 @@ impl Evm { /// Returns the active execution inspector mutably. #[inline] pub fn inspector_mut(&mut self) -> Option<&mut dyn Inspector> { + self.registered_inspector_config = None; self.inspector.as_deref_mut() } @@ -298,24 +301,27 @@ impl Evm { /// Sets the active execution inspector. #[inline] pub fn set_inspector + 'static>(&mut self, inspector: I) { + self.registered_inspector_config = None; self.inspector = Some(Box::new(inspector)); } /// Sets the active boxed execution inspector. #[inline] pub fn set_boxed_inspector(&mut self, inspector: Box>) { + self.registered_inspector_config = None; self.inspector = Some(inspector); } /// Removes the active execution inspector. #[inline] pub fn clear_inspector(&mut self) -> Option>> { + self.registered_inspector_config = None; self.inspector.take() } /// Returns the active EVM version. #[inline] - pub const fn version(&self) -> &crate::Version { + pub fn version(&self) -> &crate::Version { self.execution_config.version() } @@ -339,7 +345,7 @@ impl Evm { /// Returns the active base specification ID. #[inline] - pub const fn spec_id(&self) -> SpecId { + pub fn spec_id(&self) -> SpecId { self.version().spec_id } @@ -694,8 +700,10 @@ impl> Evm { caller_is_static: bool, ) -> InstrStop { let mut interpreter = self.interpreter_pool.pop(); + let interpreter_ref = interpreter.as_mut(); interpreter_ref.init(bytecode, tx_env, message, caller_is_static); + self.register_inspector(); // SAFETY: `execution_config` points to a private field that host execution does not // replace or mutate, so the pointee remains valid here. let execution_config = unsafe { trustme::decouple_lt(&self.execution_config) }; @@ -710,10 +718,23 @@ impl> Evm { } else { interpreter_ref.run(execution_config, self) }; + self.interpreter_pool.push(interpreter); stop } + fn register_inspector(&mut self) { + let Some(inspector) = &self.inspector else { + return; + }; + let inspector_config = inspector.config(); + if self.registered_inspector_config == Some(inspector_config) { + return; + } + self.execution_config.register_inspector(&inspector_config); + self.registered_inspector_config = Some(inspector_config); + } + fn inspect_initialize_interp(&mut self, interp: &mut Interpreter<'_, T>) { if let Some(inspector) = &mut self.inspector { inspector.initialize_interp(interp); diff --git a/crates/evm2/src/interpreter/dispatch/mod.rs b/crates/evm2/src/interpreter/dispatch/mod.rs index 14027e3e..a98fee9a 100644 --- a/crates/evm2/src/interpreter/dispatch/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/mod.rs @@ -2,7 +2,7 @@ use crate::{ BaseEvmConfigSelector, EvmConfig, EvmConfigSelector, EvmTypes, OpcodeConfig, - evm::config::SelectorOpcodeConfig, + evm::{config::SelectorOpcodeConfig, inspector::OpcodeSet}, interpreter::{Interpreter, InterpreterState, Pc, Stack, op}, trustme, }; @@ -52,6 +52,21 @@ const fn instruction_len(op: u8) -> usize { /// Instruction dispatch table. pub(crate) type InstrTable = imp::RawInstrTable; +pub(crate) fn make_inspect_table( + instructions: &InstrTable, + inspect_instructions: &InstrTable, + step_opcodes: &OpcodeSet, +) -> InstrTable { + if *step_opcodes == OpcodeSet::ALL { + return *inspect_instructions; + } + let mut table = *instructions; + for opcode in step_opcodes.bits() { + table[opcode as usize] = inspect_instructions[opcode as usize]; + } + table +} + const fn make_table( previous: Option<&InstrTable>, previous_opcode_config: Option<&OpcodeConfig>, diff --git a/crates/evm2/src/interpreter/dispatch/table/mod.rs b/crates/evm2/src/interpreter/dispatch/table/mod.rs index f8c93f79..d60ba008 100644 --- a/crates/evm2/src/interpreter/dispatch/table/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/table/mod.rs @@ -1,4 +1,4 @@ -use super::{DynInspector, InspectMode, NoInspector, inc_pc, run_state}; +use super::{InspectMode, inc_pc, run_state}; use crate::{ EvmConfig, EvmTypes, interpreter::{InstrStop, Interpreter, InterpreterState, Pc, Result, Stack, StackMut}, @@ -30,6 +30,8 @@ trait DispatchGas: Copy { op: u8, ) -> Result; + fn sync_before_inspect(&self, state: &mut InterpreterState<'_, T>); + fn sync_before_exec(&self, state: &mut InterpreterState<'_, T>, dynamic_gas: bool); fn sync_after_exec( @@ -49,6 +51,9 @@ impl DispatchGas for () { state.gas_mut().spend(C::OPCODE_CONFIG.static_gas(op) as _) } + #[inline(always)] + fn sync_before_inspect(&self, _state: &mut InterpreterState<'_, T>) {} + #[inline(always)] fn sync_before_exec( &self, @@ -75,6 +80,14 @@ fn dispatch_inner, M: InspectMode, G: DispatchGa state: &mut InterpreterState<'_, T>, op: u8, ) -> (Pc, G) { + if M::INSPECT { + gas.sync_before_inspect(state); + M::step(state, pc, stack.len()); + if state.result().is_err() { + return (pc, gas); + } + } + let instruction = C::OPCODE_CONFIG.instruction(op); let instr = instruction.instr; let dynamic_gas = instruction.dynamic_gas; @@ -95,6 +108,8 @@ fn dispatch_inner, M: InspectMode, G: DispatchGa } if M::INSPECT { state.set_result(r); + gas.sync_before_inspect(state); + M::step_end(state, pc, stack.len()); } else if let Err(e) = r { state.set_result(Err(e)); cold_path(); @@ -109,13 +124,13 @@ pub(in crate::interpreter) fn run( ) -> InstrStop { let (state, pc, stack) = run_state(interpreter); if state.is_inspecting() { - return run_inner::(state, pc, stack, instructions); + return run_inner::(state, pc, stack, instructions); } - run_inner::(state, pc, stack, instructions) + run_inner::(state, pc, stack, instructions) } #[allow(clippy::let_unit_value)] -fn run_inner>( +fn run_inner( state: &mut InterpreterState<'_, T>, mut pc: Pc, mut stack: Stack<'_>, @@ -123,14 +138,6 @@ fn run_inner>( ) -> InstrStop { let mut loop_state = imp::loop_state(state.gas_mut()); loop { - if M::INSPECT { - imp::sync_loop_state(state, loop_state); - M::step(state, pc, stack.len); - if state.result().is_err() { - return finish_run(state, pc, stack.len, loop_state); - } - } - let op = pc.op(); let instr = instructions[op as usize]; let (next_pc, next_stack_len) = @@ -138,9 +145,8 @@ fn run_inner>( pc = next_pc; stack.len = next_stack_len; - if M::INSPECT { + if INSPECTING { imp::sync_loop_state(state, loop_state); - M::step_end(state, pc, stack.len); if state.result().is_err() { return finish_run(state, pc, stack.len, loop_state); } diff --git a/crates/evm2/src/interpreter/dispatch/table/packed.rs b/crates/evm2/src/interpreter/dispatch/table/packed.rs index 15932cad..b9f677cf 100644 --- a/crates/evm2/src/interpreter/dispatch/table/packed.rs +++ b/crates/evm2/src/interpreter/dispatch/table/packed.rs @@ -63,6 +63,11 @@ impl super::DispatchGas for RemainingGas { self.spend(C::OPCODE_CONFIG.static_gas(op) as _) } + #[inline(always)] + fn sync_before_inspect(&self, state: &mut InterpreterState<'_, T>) { + state.gas_mut().set_remaining(self.get()); + } + #[inline(always)] fn sync_before_exec( &self, diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index b1b1db55..12cbcba3 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -183,10 +183,12 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { /// Runs the interpreter until it stops. #[inline] pub fn run(&mut self, config: &ExecutionConfig, host: &mut T::Host) -> InstrStop { - self.run_inner(config.version(), host, None, config.instructions) + self.run_inner(config.version(), host, None, config.instructions()) } /// Runs the interpreter until it stops with an execution inspector. + /// + /// `config` must already be registered with [`Inspector::config`] for `inspector`. #[inline] pub fn run_inspect( &mut self, @@ -198,7 +200,7 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { config.version(), host, Some(NonNull::from(inspector)), - config.inspect_instructions, + config.inspect_instructions(), ) } From c97982cd3478034aca4537b525e6d084b1d44f06 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 29 May 2026 03:13:06 +0200 Subject: [PATCH 02/17] perf(inspector): run full steps in dispatch loop --- crates/evm2/src/evm/config.rs | 21 +++++++++++++- crates/evm2/src/interpreter/dispatch/mod.rs | 3 -- .../src/interpreter/dispatch/table/mod.rs | 29 +++++++++++++++---- crates/evm2/src/interpreter/runtime.rs | 13 ++++++++- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index 07ad5558..a91c828b 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -3,7 +3,7 @@ use crate::{ OpcodeConfig, SpecId, ethereum::RecoveredTxEnvelope, - evm::inspector::InspectorConfig, + evm::inspector::{InspectorConfig, OpcodeSet}, interpreter::{ Host, dispatch::{self, ConfigInstrTables, InstrTable, SelectorInstrTables}, @@ -131,6 +131,7 @@ struct ExecutionConfigInner { inspect_instructions: InstrTable, #[derive_where(skip)] inspect_instruction_source: &'static InstrTable, + inspect_steps_in_loop: bool, } impl Clone for ExecutionConfig { @@ -148,6 +149,7 @@ impl Clone for ExecutionConfigInner { instructions: self.instructions, inspect_instructions: self.inspect_instructions, inspect_instruction_source: self.inspect_instruction_source, + inspect_steps_in_loop: self.inspect_steps_in_loop, } } } @@ -174,6 +176,7 @@ impl ExecutionConfig { instructions: &SelectorInstrTables::::INSTRUCTIONS[i], inspect_instructions: *inspect_instruction_source, inspect_instruction_source, + inspect_steps_in_loop: false, }), } } @@ -188,6 +191,7 @@ impl ExecutionConfig { instructions: ConfigInstrTables::::INSTRUCTIONS, inspect_instructions: *inspect_instruction_source, inspect_instruction_source, + inspect_steps_in_loop: false, }), } } @@ -226,12 +230,27 @@ impl ExecutionConfig { &self.inner.inspect_instructions } + #[inline] + pub(crate) fn inspect_steps_in_loop(&self) -> bool { + self.inner.inspect_steps_in_loop + } + pub(crate) fn register_inspector(&mut self, inspector_config: &InspectorConfig) { + if inspector_config.set == OpcodeSet::ALL { + self.inner.inspect_instructions = if cfg!(tco) { + *self.inner.inspect_instruction_source + } else { + *self.inner.instructions + }; + self.inner.inspect_steps_in_loop = !cfg!(tco); + return; + } self.inner.inspect_instructions = dispatch::make_inspect_table( self.inner.instructions, self.inner.inspect_instruction_source, &inspector_config.set, ); + self.inner.inspect_steps_in_loop = false; } } diff --git a/crates/evm2/src/interpreter/dispatch/mod.rs b/crates/evm2/src/interpreter/dispatch/mod.rs index a98fee9a..00a805fd 100644 --- a/crates/evm2/src/interpreter/dispatch/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/mod.rs @@ -57,9 +57,6 @@ pub(crate) fn make_inspect_table( inspect_instructions: &InstrTable, step_opcodes: &OpcodeSet, ) -> InstrTable { - if *step_opcodes == OpcodeSet::ALL { - return *inspect_instructions; - } let mut table = *instructions; for opcode in step_opcodes.bits() { table[opcode as usize] = inspect_instructions[opcode as usize]; diff --git a/crates/evm2/src/interpreter/dispatch/table/mod.rs b/crates/evm2/src/interpreter/dispatch/table/mod.rs index d60ba008..f1f2f35d 100644 --- a/crates/evm2/src/interpreter/dispatch/table/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/table/mod.rs @@ -1,4 +1,4 @@ -use super::{InspectMode, inc_pc, run_state}; +use super::{DynInspector, InspectMode, inc_pc, run_state}; use crate::{ EvmConfig, EvmTypes, interpreter::{InstrStop, Interpreter, InterpreterState, Pc, Result, Stack, StackMut}, @@ -113,6 +113,9 @@ fn dispatch_inner, M: InspectMode, G: DispatchGa } else if let Err(e) = r { state.set_result(Err(e)); cold_path(); + if state.inspect_steps_in_loop() { + return (pc, gas); + } return (Pc::new(core::ptr::null()), gas); } (pc, gas) @@ -124,13 +127,16 @@ pub(in crate::interpreter) fn run( ) -> InstrStop { let (state, pc, stack) = run_state(interpreter); if state.is_inspecting() { - return run_inner::(state, pc, stack, instructions); + if state.inspect_steps_in_loop() { + return run_inner::(state, pc, stack, instructions); + } + return run_inner::(state, pc, stack, instructions); } - run_inner::(state, pc, stack, instructions) + run_inner::(state, pc, stack, instructions) } #[allow(clippy::let_unit_value)] -fn run_inner( +fn run_inner( state: &mut InterpreterState<'_, T>, mut pc: Pc, mut stack: Stack<'_>, @@ -140,12 +146,25 @@ fn run_inner( loop { let op = pc.op(); let instr = instructions[op as usize]; + if LOOP_INSPECT { + imp::sync_loop_state(state, loop_state); + >::step(state, pc, stack.len); + if state.result().is_err() { + return finish_run(state, pc, stack.len, loop_state); + } + } let (next_pc, next_stack_len) = imp::dispatch_loop_call(instr, pc, stack.reborrow(), state, &mut loop_state); pc = next_pc; stack.len = next_stack_len; - if INSPECTING { + if LOOP_INSPECT { + imp::sync_loop_state(state, loop_state); + >::step_end(state, pc, stack.len); + if state.result().is_err() { + return finish_run(state, pc, stack.len, loop_state); + } + } else if INSPECTING { imp::sync_loop_state(state, loop_state); if state.result().is_err() { return finish_run(state, pc, stack.len, loop_state); diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index 12cbcba3..50f6569f 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -41,6 +41,7 @@ pub struct Interpreter<'frame, T: EvmTypes> { spec: SpecId, features: EvmFeatures, is_static: bool, + inspect_steps_in_loop: bool, } // SAFETY: The interpreter's internal pointers are always valid. `pc` points into owned bytecode, @@ -68,6 +69,7 @@ impl Default for Interpreter<'_, T> { version: core::ptr::null(), spec: SpecId::DEFAULT, features: EvmFeatures::empty(), + inspect_steps_in_loop: false, // SAFETY: `MaybeUninit` does not need initialization. stack: unsafe { Box::new_uninit().assume_init() }, } @@ -183,7 +185,7 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { /// Runs the interpreter until it stops. #[inline] pub fn run(&mut self, config: &ExecutionConfig, host: &mut T::Host) -> InstrStop { - self.run_inner(config.version(), host, None, config.instructions()) + self.run_inner(config.version(), host, None, config.instructions(), false) } /// Runs the interpreter until it stops with an execution inspector. @@ -201,6 +203,7 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { host, Some(NonNull::from(inspector)), config.inspect_instructions(), + config.inspect_steps_in_loop(), ) } @@ -211,11 +214,13 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { host: &mut T::Host, inspector: Option>>, instructions: &InstrTable, + inspect_steps_in_loop: bool, ) -> InstrStop { self.memory.set_memory_limit(version.memory_limit); self.host = Some(NonNull::from(host)); self.inspector = inspector; + self.inspect_steps_in_loop = inspect_steps_in_loop; self.version = version; self.spec = version.spec_id; self.features = version.features; @@ -269,6 +274,12 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { self.0.inspector.is_some() } + #[inline] + #[cfg(not(tco))] + pub(crate) const fn inspect_steps_in_loop(&self) -> bool { + self.0.inspect_steps_in_loop + } + #[inline] pub(crate) const fn set_pc_stack_len(&mut self, pc: *const u8, stack_len: usize) { self.0.pc = pc; From a03c018008854811a4e2b8afa1053a6c33ac170a Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 29 May 2026 03:22:26 +0200 Subject: [PATCH 03/17] fix(inspector): specialize full-step dispatch --- crates/evm2/src/evm/config.rs | 19 +++---- crates/evm2/src/evm/inspector.rs | 5 +- crates/evm2/src/evm/mod.rs | 5 +- crates/evm2/src/interpreter/dispatch/mod.rs | 40 ++++++++++---- .../src/interpreter/dispatch/table/mod.rs | 24 ++++++--- .../src/interpreter/dispatch/table/packed.rs | 3 +- .../dispatch/table/single_return.rs | 4 +- .../interpreter/dispatch/table/unpacked.rs | 4 +- crates/evm2/src/interpreter/dispatch/tco.rs | 9 ++++ crates/evm2/src/interpreter/runtime.rs | 53 ++++++++++++------- 10 files changed, 111 insertions(+), 55 deletions(-) diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index a91c828b..3bc1f752 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -131,7 +131,8 @@ struct ExecutionConfigInner { inspect_instructions: InstrTable, #[derive_where(skip)] inspect_instruction_source: &'static InstrTable, - inspect_steps_in_loop: bool, + #[derive_where(skip)] + loop_inspect_instruction_source: &'static InstrTable, } impl Clone for ExecutionConfig { @@ -149,7 +150,7 @@ impl Clone for ExecutionConfigInner { instructions: self.instructions, inspect_instructions: self.inspect_instructions, inspect_instruction_source: self.inspect_instruction_source, - inspect_steps_in_loop: self.inspect_steps_in_loop, + loop_inspect_instruction_source: self.loop_inspect_instruction_source, } } } @@ -176,7 +177,8 @@ impl ExecutionConfig { instructions: &SelectorInstrTables::::INSTRUCTIONS[i], inspect_instructions: *inspect_instruction_source, inspect_instruction_source, - inspect_steps_in_loop: false, + loop_inspect_instruction_source: + &SelectorInstrTables::::LOOP_INSPECT_INSTRUCTIONS[i], }), } } @@ -191,7 +193,8 @@ impl ExecutionConfig { instructions: ConfigInstrTables::::INSTRUCTIONS, inspect_instructions: *inspect_instruction_source, inspect_instruction_source, - inspect_steps_in_loop: false, + loop_inspect_instruction_source: + ConfigInstrTables::::LOOP_INSPECT_INSTRUCTIONS, }), } } @@ -231,18 +234,13 @@ impl ExecutionConfig { } #[inline] - pub(crate) fn inspect_steps_in_loop(&self) -> bool { - self.inner.inspect_steps_in_loop - } - pub(crate) fn register_inspector(&mut self, inspector_config: &InspectorConfig) { if inspector_config.set == OpcodeSet::ALL { self.inner.inspect_instructions = if cfg!(tco) { *self.inner.inspect_instruction_source } else { - *self.inner.instructions + *self.inner.loop_inspect_instruction_source }; - self.inner.inspect_steps_in_loop = !cfg!(tco); return; } self.inner.inspect_instructions = dispatch::make_inspect_table( @@ -250,7 +248,6 @@ impl ExecutionConfig { self.inner.inspect_instruction_source, &inspector_config.set, ); - self.inner.inspect_steps_in_loop = false; } } diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index bfbbddd3..d82c3a9f 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -569,8 +569,9 @@ mod tests { message.gas_limit = gas_limit; let mut inner = Interpreter::::new(bytecode, &tx_env, &message, false); let mut config = ExecutionConfig::for_base_spec::(SpecId::OSAKA); - config.register_inspector(&inspector.config()); - let stop = inner.run_inspect(&config, host, inspector); + let inspector_config = inspector.config(); + config.register_inspector(&inspector_config); + let stop = inner.run_inspect(&config, &inspector_config, host, inspector); let stack = inner.stack().to_vec(); (stop, stack) } diff --git a/crates/evm2/src/evm/mod.rs b/crates/evm2/src/evm/mod.rs index 13237f19..ba1c389a 100644 --- a/crates/evm2/src/evm/mod.rs +++ b/crates/evm2/src/evm/mod.rs @@ -714,7 +714,10 @@ impl> Evm { unsafe { trustme::decouple_lt_mut(inspector) } }); let stop = if let Some(inspector) = inspector { - interpreter_ref.run_inspect(execution_config, self, inspector) + let inspector_config = self + .registered_inspector_config + .expect("inspector config must be registered before run_inspect"); + interpreter_ref.run_inspect(execution_config, &inspector_config, self, inspector) } else { interpreter_ref.run(execution_config, self) }; diff --git a/crates/evm2/src/interpreter/dispatch/mod.rs b/crates/evm2/src/interpreter/dispatch/mod.rs index 00a805fd..85c0665f 100644 --- a/crates/evm2/src/interpreter/dispatch/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/mod.rs @@ -17,7 +17,7 @@ cfg_if::cfg_if! { } } -pub(in crate::interpreter) use imp::run; +pub(in crate::interpreter) use imp::{run, run_inspect_loop}; #[inline(always)] fn run_state<'a, 'frame, T: EvmTypes>( @@ -64,7 +64,7 @@ pub(crate) fn make_inspect_table( table } -const fn make_table( +const fn make_table( previous: Option<&InstrTable>, previous_opcode_config: Option<&OpcodeConfig>, ) -> InstrTable @@ -75,7 +75,9 @@ where { let mut table = match previous { Some(previous) => *previous, - None => [imp::dispatch:: as imp::RawInstrFn; 256], + None => { + [imp::dispatch:: as imp::RawInstrFn; 256] + } }; let vt = C::OPCODE_CONFIG; @@ -83,7 +85,7 @@ where ($($op:literal,)*) => { $( if instruction_changed(vt, previous_opcode_config, $op) && !vt.is_unknown_opcode($op) { - table[$op] = imp::dispatch:: as imp::RawInstrFn; + table[$op] = imp::dispatch:: as imp::RawInstrFn; } )* }; @@ -132,7 +134,7 @@ where table } -const fn make_selector_tables() +const fn make_selector_tables() -> [InstrTable; crate::SpecId::COUNT] where T: EvmTypes, @@ -146,7 +148,7 @@ where (@build [$($tables:ident,)*] [$($previous_table:tt)*]; $spec:ident $name:ident, $($rest:ident $rest_name:ident,)*) => {{ let spec = crate::SpecId::$spec; let previous = spec.prev(); - let $name = make_table::, M>( + let $name = make_table::, M, NULL_ON_ERROR>( make_selector_tables!(@previous_table [$($previous_table)*]), match previous { Some(previous) => { @@ -178,7 +180,7 @@ where T: EvmTypes, C: EvmConfig, { - pub(crate) const INSTRUCTIONS: &'static InstrTable = &make_table::( + pub(crate) const INSTRUCTIONS: &'static InstrTable = &make_table::( Some( &SelectorInstrTables::::INSTRUCTIONS [C::BASE_SPEC_ID as usize], @@ -188,7 +190,12 @@ where [C::BASE_SPEC_ID as usize], ), ); - pub(crate) const INSPECT_INSTRUCTIONS: &'static InstrTable = &make_table::( + pub(crate) const INSPECT_INSTRUCTIONS: &'static InstrTable = &make_table::< + T, + C, + DynInspector, + true, + >( Some( &SelectorInstrTables::::INSPECT_INSTRUCTIONS [C::BASE_SPEC_ID as usize], @@ -198,6 +205,17 @@ where [C::BASE_SPEC_ID as usize], ), ); + pub(crate) const LOOP_INSPECT_INSTRUCTIONS: &'static InstrTable = + &make_table::( + Some( + &SelectorInstrTables::::LOOP_INSPECT_INSTRUCTIONS + [C::BASE_SPEC_ID as usize], + ), + Some( + SelectorOpcodeConfig::::OPCODE_CONFIG + [C::BASE_SPEC_ID as usize], + ), + ); } pub(crate) struct SelectorInstrTables( @@ -210,9 +228,11 @@ where F: EvmConfigSelector, { pub(crate) const INSTRUCTIONS: &'static [InstrTable; crate::SpecId::COUNT] = - &make_selector_tables::(); + &make_selector_tables::(); pub(crate) const INSPECT_INSTRUCTIONS: &'static [InstrTable; crate::SpecId::COUNT] = - &make_selector_tables::(); + &make_selector_tables::(); + pub(crate) const LOOP_INSPECT_INSTRUCTIONS: &'static [InstrTable; crate::SpecId::COUNT] = + &make_selector_tables::(); } const fn instruction_changed( diff --git a/crates/evm2/src/interpreter/dispatch/table/mod.rs b/crates/evm2/src/interpreter/dispatch/table/mod.rs index f1f2f35d..4a386977 100644 --- a/crates/evm2/src/interpreter/dispatch/table/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/table/mod.rs @@ -73,7 +73,13 @@ impl DispatchGas for () { #[cold] // Not cold, but avoids MIR inlining. #[inline(always)] -fn dispatch_inner, M: InspectMode, G: DispatchGas>( +fn dispatch_inner< + T: EvmTypes, + C: EvmConfig, + M: InspectMode, + G: DispatchGas, + const NULL_ON_ERROR: bool, +>( mut pc: Pc, mut stack: StackMut<'_>, mut gas: G, @@ -113,10 +119,7 @@ fn dispatch_inner, M: InspectMode, G: DispatchGa } else if let Err(e) = r { state.set_result(Err(e)); cold_path(); - if state.inspect_steps_in_loop() { - return (pc, gas); - } - return (Pc::new(core::ptr::null()), gas); + return (if NULL_ON_ERROR { Pc::new(core::ptr::null()) } else { pc }, gas); } (pc, gas) } @@ -127,14 +130,19 @@ pub(in crate::interpreter) fn run( ) -> InstrStop { let (state, pc, stack) = run_state(interpreter); if state.is_inspecting() { - if state.inspect_steps_in_loop() { - return run_inner::(state, pc, stack, instructions); - } return run_inner::(state, pc, stack, instructions); } run_inner::(state, pc, stack, instructions) } +pub(in crate::interpreter) fn run_inspect_loop( + interpreter: &mut Interpreter<'_, T>, + instructions: &RawInstrTable, +) -> InstrStop { + let (state, pc, stack) = run_state(interpreter); + run_inner::(state, pc, stack, instructions) +} + #[allow(clippy::let_unit_value)] fn run_inner( state: &mut InterpreterState<'_, T>, diff --git a/crates/evm2/src/interpreter/dispatch/table/packed.rs b/crates/evm2/src/interpreter/dispatch/table/packed.rs index b9f677cf..a64d8579 100644 --- a/crates/evm2/src/interpreter/dispatch/table/packed.rs +++ b/crates/evm2/src/interpreter/dispatch/table/packed.rs @@ -96,6 +96,7 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: super::InspectMode, + const NULL_ON_ERROR: bool, const OP: u8, >( pc: Pc, @@ -105,7 +106,7 @@ extern_table! { ) -> InstrFnRet { let initial_remaining_gas = remaining_gas; let (pc, remaining_gas) = - super::dispatch_inner::( + super::dispatch_inner::( pc, stack.as_mut(), remaining_gas, diff --git a/crates/evm2/src/interpreter/dispatch/table/single_return.rs b/crates/evm2/src/interpreter/dispatch/table/single_return.rs index 2cf437d5..4fbbe1fe 100644 --- a/crates/evm2/src/interpreter/dispatch/table/single_return.rs +++ b/crates/evm2/src/interpreter/dispatch/table/single_return.rs @@ -39,13 +39,15 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: super::InspectMode, + const NULL_ON_ERROR: bool, const OP: u8, >( pc: Pc, stack: StackMut<'_>, state: &mut InterpreterState<'_, T>, ) -> Pc { - let (pc, ()) = super::dispatch_inner::(pc, stack, (), state, OP); + let (pc, ()) = + super::dispatch_inner::(pc, stack, (), state, OP); pc } } diff --git a/crates/evm2/src/interpreter/dispatch/table/unpacked.rs b/crates/evm2/src/interpreter/dispatch/table/unpacked.rs index e5a7322a..946c12fd 100644 --- a/crates/evm2/src/interpreter/dispatch/table/unpacked.rs +++ b/crates/evm2/src/interpreter/dispatch/table/unpacked.rs @@ -41,13 +41,15 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: super::InspectMode, + const NULL_ON_ERROR: bool, const OP: u8, >( pc: Pc, mut stack: Stack<'_>, state: &mut InterpreterState<'_, T>, ) -> InstrFnRet { - let (pc, ()) = super::dispatch_inner::(pc, stack.as_mut(), (), state, OP); + let (pc, ()) = + super::dispatch_inner::(pc, stack.as_mut(), (), state, OP); (pc, stack.len) } } diff --git a/crates/evm2/src/interpreter/dispatch/tco.rs b/crates/evm2/src/interpreter/dispatch/tco.rs index a8c22383..3e5331c5 100644 --- a/crates/evm2/src/interpreter/dispatch/tco.rs +++ b/crates/evm2/src/interpreter/dispatch/tco.rs @@ -39,11 +39,20 @@ pub(in crate::interpreter) fn run( state.result().unwrap_err() } +#[inline(always)] +pub(in crate::interpreter) fn run_inspect_loop( + interpreter: &mut Interpreter<'_, T>, + instructions: &RawInstrTable, +) -> InstrStop { + run(interpreter, instructions) +} + extern_table! { pub(super) fn dispatch< T: EvmTypes, C: EvmConfig, M: InspectMode, + const NULL_ON_ERROR: bool, const OP: u8, >( mut pc: Pc, diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index 50f6569f..6a4fa8a1 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -6,7 +6,7 @@ use crate::{ EvmTypes, ExecutionConfig, SpecId, Version, bytecode::Bytecode, env::TxEnv, - evm::inspector::Inspector, + evm::inspector::{Inspector, InspectorConfig, OpcodeSet}, interpreter::dispatch::{self, InstrTable}, trustme, version::{EvmFeatures, GasParams}, @@ -41,7 +41,6 @@ pub struct Interpreter<'frame, T: EvmTypes> { spec: SpecId, features: EvmFeatures, is_static: bool, - inspect_steps_in_loop: bool, } // SAFETY: The interpreter's internal pointers are always valid. `pc` points into owned bytecode, @@ -69,7 +68,6 @@ impl Default for Interpreter<'_, T> { version: core::ptr::null(), spec: SpecId::DEFAULT, features: EvmFeatures::empty(), - inspect_steps_in_loop: false, // SAFETY: `MaybeUninit` does not need initialization. stack: unsafe { Box::new_uninit().assume_init() }, } @@ -185,26 +183,30 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { /// Runs the interpreter until it stops. #[inline] pub fn run(&mut self, config: &ExecutionConfig, host: &mut T::Host) -> InstrStop { - self.run_inner(config.version(), host, None, config.instructions(), false) + self.run_inner(config.version(), host, None, config.instructions()) } /// Runs the interpreter until it stops with an execution inspector. /// - /// `config` must already be registered with [`Inspector::config`] for `inspector`. + /// `config` must already be registered with `inspector_config` for `inspector`. #[inline] pub fn run_inspect( &mut self, config: &ExecutionConfig, + inspector_config: &InspectorConfig, host: &mut T::Host, inspector: &mut dyn Inspector, ) -> InstrStop { - self.run_inner( - config.version(), - host, - Some(NonNull::from(inspector)), - config.inspect_instructions(), - config.inspect_steps_in_loop(), - ) + let inspector = Some(NonNull::from(inspector)); + if inspector_config.set == OpcodeSet::ALL && !cfg!(tco) { + return self.run_inner_inspect_loop( + config.version(), + host, + inspector, + config.inspect_instructions(), + ); + } + self.run_inner(config.version(), host, inspector, config.inspect_instructions()) } #[inline(never)] @@ -214,19 +216,36 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { host: &mut T::Host, inspector: Option>>, instructions: &InstrTable, - inspect_steps_in_loop: bool, ) -> InstrStop { self.memory.set_memory_limit(version.memory_limit); self.host = Some(NonNull::from(host)); self.inspector = inspector; - self.inspect_steps_in_loop = inspect_steps_in_loop; self.version = version; self.spec = version.spec_id; self.features = version.features; dispatch::run(self, instructions) } + + #[inline(never)] + fn run_inner_inspect_loop( + &mut self, + version: &Version, + host: &mut T::Host, + inspector: Option>>, + instructions: &InstrTable, + ) -> InstrStop { + self.memory.set_memory_limit(version.memory_limit); + + self.host = Some(NonNull::from(host)); + self.inspector = inspector; + self.version = version; + self.spec = version.spec_id; + self.features = version.features; + + dispatch::run_inspect_loop(self, instructions) + } } /// Interpreter state exposed to instruction implementations. @@ -274,12 +293,6 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { self.0.inspector.is_some() } - #[inline] - #[cfg(not(tco))] - pub(crate) const fn inspect_steps_in_loop(&self) -> bool { - self.0.inspect_steps_in_loop - } - #[inline] pub(crate) const fn set_pc_stack_len(&mut self, pc: *const u8, stack_len: usize) { self.0.pc = pc; From 02d0ec5ae4ddf06959fb18460d6338f14644beb0 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 29 May 2026 03:28:54 +0200 Subject: [PATCH 04/17] refactor(inspector): use normal table for full steps --- crates/evm2/src/evm/config.rs | 9 +---- crates/evm2/src/interpreter/dispatch/mod.rs | 38 +++++-------------- .../src/interpreter/dispatch/table/mod.rs | 14 +++---- .../src/interpreter/dispatch/table/packed.rs | 3 +- .../dispatch/table/single_return.rs | 4 +- .../interpreter/dispatch/table/unpacked.rs | 4 +- crates/evm2/src/interpreter/dispatch/tco.rs | 1 - crates/evm2/src/interpreter/runtime.rs | 2 +- 8 files changed, 20 insertions(+), 55 deletions(-) diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index 3bc1f752..40b34fdd 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -131,8 +131,6 @@ struct ExecutionConfigInner { inspect_instructions: InstrTable, #[derive_where(skip)] inspect_instruction_source: &'static InstrTable, - #[derive_where(skip)] - loop_inspect_instruction_source: &'static InstrTable, } impl Clone for ExecutionConfig { @@ -150,7 +148,6 @@ impl Clone for ExecutionConfigInner { instructions: self.instructions, inspect_instructions: self.inspect_instructions, inspect_instruction_source: self.inspect_instruction_source, - loop_inspect_instruction_source: self.loop_inspect_instruction_source, } } } @@ -177,8 +174,6 @@ impl ExecutionConfig { instructions: &SelectorInstrTables::::INSTRUCTIONS[i], inspect_instructions: *inspect_instruction_source, inspect_instruction_source, - loop_inspect_instruction_source: - &SelectorInstrTables::::LOOP_INSPECT_INSTRUCTIONS[i], }), } } @@ -193,8 +188,6 @@ impl ExecutionConfig { instructions: ConfigInstrTables::::INSTRUCTIONS, inspect_instructions: *inspect_instruction_source, inspect_instruction_source, - loop_inspect_instruction_source: - ConfigInstrTables::::LOOP_INSPECT_INSTRUCTIONS, }), } } @@ -239,7 +232,7 @@ impl ExecutionConfig { self.inner.inspect_instructions = if cfg!(tco) { *self.inner.inspect_instruction_source } else { - *self.inner.loop_inspect_instruction_source + *self.inner.instructions }; return; } diff --git a/crates/evm2/src/interpreter/dispatch/mod.rs b/crates/evm2/src/interpreter/dispatch/mod.rs index 85c0665f..f0e77bfc 100644 --- a/crates/evm2/src/interpreter/dispatch/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/mod.rs @@ -64,7 +64,7 @@ pub(crate) fn make_inspect_table( table } -const fn make_table( +const fn make_table( previous: Option<&InstrTable>, previous_opcode_config: Option<&OpcodeConfig>, ) -> InstrTable @@ -75,9 +75,7 @@ where { let mut table = match previous { Some(previous) => *previous, - None => { - [imp::dispatch:: as imp::RawInstrFn; 256] - } + None => [imp::dispatch:: as imp::RawInstrFn; 256], }; let vt = C::OPCODE_CONFIG; @@ -85,7 +83,7 @@ where ($($op:literal,)*) => { $( if instruction_changed(vt, previous_opcode_config, $op) && !vt.is_unknown_opcode($op) { - table[$op] = imp::dispatch:: as imp::RawInstrFn; + table[$op] = imp::dispatch:: as imp::RawInstrFn; } )* }; @@ -134,7 +132,7 @@ where table } -const fn make_selector_tables() +const fn make_selector_tables() -> [InstrTable; crate::SpecId::COUNT] where T: EvmTypes, @@ -148,7 +146,7 @@ where (@build [$($tables:ident,)*] [$($previous_table:tt)*]; $spec:ident $name:ident, $($rest:ident $rest_name:ident,)*) => {{ let spec = crate::SpecId::$spec; let previous = spec.prev(); - let $name = make_table::, M, NULL_ON_ERROR>( + let $name = make_table::, M>( make_selector_tables!(@previous_table [$($previous_table)*]), match previous { Some(previous) => { @@ -180,7 +178,7 @@ where T: EvmTypes, C: EvmConfig, { - pub(crate) const INSTRUCTIONS: &'static InstrTable = &make_table::( + pub(crate) const INSTRUCTIONS: &'static InstrTable = &make_table::( Some( &SelectorInstrTables::::INSTRUCTIONS [C::BASE_SPEC_ID as usize], @@ -190,12 +188,7 @@ where [C::BASE_SPEC_ID as usize], ), ); - pub(crate) const INSPECT_INSTRUCTIONS: &'static InstrTable = &make_table::< - T, - C, - DynInspector, - true, - >( + pub(crate) const INSPECT_INSTRUCTIONS: &'static InstrTable = &make_table::( Some( &SelectorInstrTables::::INSPECT_INSTRUCTIONS [C::BASE_SPEC_ID as usize], @@ -205,17 +198,6 @@ where [C::BASE_SPEC_ID as usize], ), ); - pub(crate) const LOOP_INSPECT_INSTRUCTIONS: &'static InstrTable = - &make_table::( - Some( - &SelectorInstrTables::::LOOP_INSPECT_INSTRUCTIONS - [C::BASE_SPEC_ID as usize], - ), - Some( - SelectorOpcodeConfig::::OPCODE_CONFIG - [C::BASE_SPEC_ID as usize], - ), - ); } pub(crate) struct SelectorInstrTables( @@ -228,11 +210,9 @@ where F: EvmConfigSelector, { pub(crate) const INSTRUCTIONS: &'static [InstrTable; crate::SpecId::COUNT] = - &make_selector_tables::(); + &make_selector_tables::(); pub(crate) const INSPECT_INSTRUCTIONS: &'static [InstrTable; crate::SpecId::COUNT] = - &make_selector_tables::(); - pub(crate) const LOOP_INSPECT_INSTRUCTIONS: &'static [InstrTable; crate::SpecId::COUNT] = - &make_selector_tables::(); + &make_selector_tables::(); } const fn instruction_changed( diff --git a/crates/evm2/src/interpreter/dispatch/table/mod.rs b/crates/evm2/src/interpreter/dispatch/table/mod.rs index 4a386977..44235883 100644 --- a/crates/evm2/src/interpreter/dispatch/table/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/table/mod.rs @@ -73,13 +73,7 @@ impl DispatchGas for () { #[cold] // Not cold, but avoids MIR inlining. #[inline(always)] -fn dispatch_inner< - T: EvmTypes, - C: EvmConfig, - M: InspectMode, - G: DispatchGas, - const NULL_ON_ERROR: bool, ->( +fn dispatch_inner, M: InspectMode, G: DispatchGas>( mut pc: Pc, mut stack: StackMut<'_>, mut gas: G, @@ -119,7 +113,7 @@ fn dispatch_inner< } else if let Err(e) = r { state.set_result(Err(e)); cold_path(); - return (if NULL_ON_ERROR { Pc::new(core::ptr::null()) } else { pc }, gas); + return (Pc::new(core::ptr::null()), gas); } (pc, gas) } @@ -152,6 +146,7 @@ fn run_inner( ) -> InstrStop { let mut loop_state = imp::loop_state(state.gas_mut()); loop { + let prev_pc = pc; let op = pc.op(); let instr = instructions[op as usize]; if LOOP_INSPECT { @@ -168,6 +163,9 @@ fn run_inner( if LOOP_INSPECT { imp::sync_loop_state(state, loop_state); + if pc.as_ptr().is_null() { + pc = prev_pc; + } >::step_end(state, pc, stack.len); if state.result().is_err() { return finish_run(state, pc, stack.len, loop_state); diff --git a/crates/evm2/src/interpreter/dispatch/table/packed.rs b/crates/evm2/src/interpreter/dispatch/table/packed.rs index a64d8579..b9f677cf 100644 --- a/crates/evm2/src/interpreter/dispatch/table/packed.rs +++ b/crates/evm2/src/interpreter/dispatch/table/packed.rs @@ -96,7 +96,6 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: super::InspectMode, - const NULL_ON_ERROR: bool, const OP: u8, >( pc: Pc, @@ -106,7 +105,7 @@ extern_table! { ) -> InstrFnRet { let initial_remaining_gas = remaining_gas; let (pc, remaining_gas) = - super::dispatch_inner::( + super::dispatch_inner::( pc, stack.as_mut(), remaining_gas, diff --git a/crates/evm2/src/interpreter/dispatch/table/single_return.rs b/crates/evm2/src/interpreter/dispatch/table/single_return.rs index 4fbbe1fe..2cf437d5 100644 --- a/crates/evm2/src/interpreter/dispatch/table/single_return.rs +++ b/crates/evm2/src/interpreter/dispatch/table/single_return.rs @@ -39,15 +39,13 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: super::InspectMode, - const NULL_ON_ERROR: bool, const OP: u8, >( pc: Pc, stack: StackMut<'_>, state: &mut InterpreterState<'_, T>, ) -> Pc { - let (pc, ()) = - super::dispatch_inner::(pc, stack, (), state, OP); + let (pc, ()) = super::dispatch_inner::(pc, stack, (), state, OP); pc } } diff --git a/crates/evm2/src/interpreter/dispatch/table/unpacked.rs b/crates/evm2/src/interpreter/dispatch/table/unpacked.rs index 946c12fd..e5a7322a 100644 --- a/crates/evm2/src/interpreter/dispatch/table/unpacked.rs +++ b/crates/evm2/src/interpreter/dispatch/table/unpacked.rs @@ -41,15 +41,13 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: super::InspectMode, - const NULL_ON_ERROR: bool, const OP: u8, >( pc: Pc, mut stack: Stack<'_>, state: &mut InterpreterState<'_, T>, ) -> InstrFnRet { - let (pc, ()) = - super::dispatch_inner::(pc, stack.as_mut(), (), state, OP); + let (pc, ()) = super::dispatch_inner::(pc, stack.as_mut(), (), state, OP); (pc, stack.len) } } diff --git a/crates/evm2/src/interpreter/dispatch/tco.rs b/crates/evm2/src/interpreter/dispatch/tco.rs index 3e5331c5..3dac37a9 100644 --- a/crates/evm2/src/interpreter/dispatch/tco.rs +++ b/crates/evm2/src/interpreter/dispatch/tco.rs @@ -52,7 +52,6 @@ extern_table! { T: EvmTypes, C: EvmConfig, M: InspectMode, - const NULL_ON_ERROR: bool, const OP: u8, >( mut pc: Pc, diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index 6a4fa8a1..96a9af9a 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -203,7 +203,7 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { config.version(), host, inspector, - config.inspect_instructions(), + config.instructions(), ); } self.run_inner(config.version(), host, inspector, config.inspect_instructions()) From 39b5ed931859e2721e42440b353a37fd75f13efe Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 29 May 2026 03:44:35 +0200 Subject: [PATCH 05/17] perf(inspector): skip steps for empty opcode set --- crates/evm2/src/evm/config.rs | 4 ++ crates/evm2/src/evm/inspector.rs | 48 +++++++++++++++++++ crates/evm2/src/interpreter/dispatch/mod.rs | 2 +- .../src/interpreter/dispatch/table/mod.rs | 8 ++++ crates/evm2/src/interpreter/dispatch/tco.rs | 8 ++++ crates/evm2/src/interpreter/runtime.rs | 27 +++++++++++ 6 files changed, 96 insertions(+), 1 deletion(-) diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index 40b34fdd..a307b2d3 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -228,6 +228,10 @@ impl ExecutionConfig { #[inline] pub(crate) fn register_inspector(&mut self, inspector_config: &InspectorConfig) { + if inspector_config.set.is_empty() { + self.inner.inspect_instructions = *self.inner.instructions; + return; + } if inspector_config.set == OpcodeSet::ALL { self.inner.inspect_instructions = if cfg!(tco) { *self.inner.inspect_instruction_source diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index d82c3a9f..d85df97a 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -484,6 +484,31 @@ mod tests { } } + #[derive(Default)] + struct EmptySetLogInspector { + steps: usize, + step_ends: usize, + logs: Vec, + } + + impl Inspector for EmptySetLogInspector { + fn config(&self) -> InspectorConfig { + InspectorConfig::new(OpcodeSet::EMPTY) + } + + fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.steps += 1; + } + + fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.step_ends += 1; + } + + fn log(&mut self, log: &Log) { + self.logs.push(log.clone()); + } + } + #[derive(Default)] struct FailingStepInspector { steps: usize, @@ -887,6 +912,29 @@ mod tests { assert_eq!(host.logs, inspector.logs); } + #[test] + fn empty_opcode_set_skips_steps_but_keeps_other_hooks() { + let contract = Address::from([0x11; 20]); + let mut host = TestHost::default(); + let mut inspector = EmptySetLogInspector::default(); + let code = Vec::from([op::PUSH1, 0, op::PUSH1, 0, op::LOG0, op::STOP]); + + let (stop, _) = run_with_inspector( + code, + &mut host, + &Message { destination: contract, ..Default::default() }, + 10_000, + &mut inspector, + ); + + assert!(matches!(stop, InstrStop::Stop)); + assert_eq!(inspector.steps, 0); + assert_eq!(inspector.step_ends, 0); + assert_eq!(inspector.logs.len(), 1); + assert_eq!(inspector.logs[0].address, contract); + assert_eq!(host.logs, inspector.logs); + } + #[test] fn log_opcode_oog_is_not_inspected_or_emitted_to_host() { let mut host = TestHost::default(); diff --git a/crates/evm2/src/interpreter/dispatch/mod.rs b/crates/evm2/src/interpreter/dispatch/mod.rs index f0e77bfc..5448b32f 100644 --- a/crates/evm2/src/interpreter/dispatch/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/mod.rs @@ -17,7 +17,7 @@ cfg_if::cfg_if! { } } -pub(in crate::interpreter) use imp::{run, run_inspect_loop}; +pub(in crate::interpreter) use imp::{run, run_inspect_loop, run_no_steps}; #[inline(always)] fn run_state<'a, 'frame, T: EvmTypes>( diff --git a/crates/evm2/src/interpreter/dispatch/table/mod.rs b/crates/evm2/src/interpreter/dispatch/table/mod.rs index 44235883..609c752f 100644 --- a/crates/evm2/src/interpreter/dispatch/table/mod.rs +++ b/crates/evm2/src/interpreter/dispatch/table/mod.rs @@ -137,6 +137,14 @@ pub(in crate::interpreter) fn run_inspect_loop( run_inner::(state, pc, stack, instructions) } +pub(in crate::interpreter) fn run_no_steps( + interpreter: &mut Interpreter<'_, T>, + instructions: &RawInstrTable, +) -> InstrStop { + let (state, pc, stack) = run_state(interpreter); + run_inner::(state, pc, stack, instructions) +} + #[allow(clippy::let_unit_value)] fn run_inner( state: &mut InterpreterState<'_, T>, diff --git a/crates/evm2/src/interpreter/dispatch/tco.rs b/crates/evm2/src/interpreter/dispatch/tco.rs index 3dac37a9..afcd2b2f 100644 --- a/crates/evm2/src/interpreter/dispatch/tco.rs +++ b/crates/evm2/src/interpreter/dispatch/tco.rs @@ -47,6 +47,14 @@ pub(in crate::interpreter) fn run_inspect_loop( run(interpreter, instructions) } +#[inline(always)] +pub(in crate::interpreter) fn run_no_steps( + interpreter: &mut Interpreter<'_, T>, + instructions: &RawInstrTable, +) -> InstrStop { + run(interpreter, instructions) +} + extern_table! { pub(super) fn dispatch< T: EvmTypes, diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index 96a9af9a..b938bcfe 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -198,6 +198,14 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { inspector: &mut dyn Inspector, ) -> InstrStop { let inspector = Some(NonNull::from(inspector)); + if inspector_config.set.is_empty() { + return self.run_inner_no_steps( + config.version(), + host, + inspector, + config.instructions(), + ); + } if inspector_config.set == OpcodeSet::ALL && !cfg!(tco) { return self.run_inner_inspect_loop( config.version(), @@ -246,6 +254,25 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { dispatch::run_inspect_loop(self, instructions) } + + #[inline(never)] + fn run_inner_no_steps( + &mut self, + version: &Version, + host: &mut T::Host, + inspector: Option>>, + instructions: &InstrTable, + ) -> InstrStop { + self.memory.set_memory_limit(version.memory_limit); + + self.host = Some(NonNull::from(host)); + self.inspector = inspector; + self.version = version; + self.spec = version.spec_id; + self.features = version.features; + + dispatch::run_no_steps(self, instructions) + } } /// Interpreter state exposed to instruction implementations. From e95cebd8a0326500042dd829bc7c6ed742a91b9b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 29 May 2026 04:37:17 +0200 Subject: [PATCH 06/17] chore(config): implement execution debug manually --- crates/evm2/src/evm/config.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index a307b2d3..1d4e816a 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -11,6 +11,7 @@ use crate::{ version::Version, }; use alloc::boxed::Box; +use core::fmt; use derive_where::derive_where; /// Runtime EVM type family. @@ -117,7 +118,6 @@ where /// /// Bundles the active runtime `Version` with the finalized instruction dispatch table selected for /// an EVM instance. This is the data passed to the interpreter when it runs. -#[derive_where(Debug)] pub struct ExecutionConfig { inner: Box>, } @@ -140,6 +140,15 @@ impl Clone for ExecutionConfig { } } +impl fmt::Debug for ExecutionConfig { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExecutionConfig") + .field("version", &self.inner.version) + .finish_non_exhaustive() + } +} + impl Clone for ExecutionConfigInner { #[inline] fn clone(&self) -> Self { From 0bfea7c104ed9cee00ae027fc9ba39e17133f098 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 06:21:18 +0200 Subject: [PATCH 07/17] refactor(inspector): add config opcode-set builder --- crates/evm2/src/evm/inspector.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index d85df97a..6f94798d 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -130,8 +130,22 @@ pub struct InspectorConfig { impl InspectorConfig { /// Creates an inspector configuration. #[inline] - pub const fn new(set: OpcodeSet) -> Self { - Self { set, _non_exhaustive: () } + pub const fn new() -> Self { + Self { set: OpcodeSet::ALL, _non_exhaustive: () } + } + + /// Sets the opcodes for which step hooks are enabled. + #[inline] + pub const fn with_opcode_set(mut self, set: OpcodeSet) -> Self { + self.set = set; + self + } +} + +impl Default for InspectorConfig { + #[inline] + fn default() -> Self { + Self::new() } } @@ -140,7 +154,7 @@ pub trait Inspector: Any + Send { /// Returns this inspector's execution configuration. #[inline] fn config(&self) -> InspectorConfig { - InspectorConfig::new(OpcodeSet::ALL) + InspectorConfig::new() } /// Called after a frame interpreter has been initialized. @@ -315,7 +329,7 @@ mod tests { fn config(&self) -> InspectorConfig { let mut set = OpcodeSet::EMPTY; set.insert(op::ADD); - InspectorConfig::new(set) + InspectorConfig::new().with_opcode_set(set) } fn step(&mut self, interp: &mut Interpreter<'_, T>) { @@ -493,7 +507,7 @@ mod tests { impl Inspector for EmptySetLogInspector { fn config(&self) -> InspectorConfig { - InspectorConfig::new(OpcodeSet::EMPTY) + InspectorConfig::new().with_opcode_set(OpcodeSet::EMPTY) } fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { From 6d1b432c73f7b0ee9d8c3fdc95e3221a64123897 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 07:28:34 +0200 Subject: [PATCH 08/17] feat(inspector): request config reconfigure --- crates/evm2/src/evm/inspector.rs | 67 ++++++++++++++++++++++++++ crates/evm2/src/evm/mod.rs | 44 +++++++++++------ crates/evm2/src/interpreter/host.rs | 4 ++ crates/evm2/src/interpreter/runtime.rs | 12 ++++- 4 files changed, 111 insertions(+), 16 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 6f94798d..311cb06a 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -342,6 +342,34 @@ mod tests { } } + struct ReconfiguringInspector { + set: OpcodeSet, + steps: usize, + opcodes: Vec, + } + + impl Default for ReconfiguringInspector { + fn default() -> Self { + Self { set: OpcodeSet::EMPTY, steps: 0, opcodes: Vec::new() } + } + } + + impl Inspector for ReconfiguringInspector { + fn config(&self) -> InspectorConfig { + InspectorConfig::new().with_opcode_set(self.set) + } + + fn initialize_interp(&mut self, interp: &mut Interpreter<'_, BaseEvmTypes>) { + self.set.insert(op::ADD); + interp.request_inspector_reconfigure(); + } + + fn step(&mut self, interp: &mut Interpreter<'_, BaseEvmTypes>) { + self.steps += 1; + self.opcodes.push(interp.opcode()); + } + } + #[derive(Default)] struct MessageInspector { call_depth: Option, @@ -1116,6 +1144,45 @@ mod tests { assert_eq!(inspector.opcodes, [op::ADD]); } + #[test] + fn evm_transaction_reconfigures_inspector_from_initialize() { + let caller = Address::from([0xaa; 20]); + let contract = Address::from([0xbb; 20]); + let code = Bytecode::new_legacy(Bytes::from_static(&[ + op::PUSH1, + 1, + op::PUSH1, + 2, + op::ADD, + op::STOP, + ])); + let mut database = InMemoryDB::default(); + database.insert_account_info( + &caller, + AccountInfo::default().with_balance(U256::from(1_000_000_000_u64)), + ); + database.insert_account_info(&contract, AccountInfo::default().with_code(code)); + let mut evm = Evm::::new( + SpecId::OSAKA, + BlockEnv::default(), + ethereum_tx_registry(SpecId::OSAKA), + database, + Precompiles::base(SpecId::OSAKA), + ); + evm.set_inspector(ReconfiguringInspector::default()); + let tx = RecoveredTxEnvelope::Legacy(Recovered::new_unchecked( + TxLegacy { to: TxKind::Call(contract), gas_limit: 100_000, ..Default::default() }, + caller, + )); + + let result = evm.transact(&tx).unwrap(); + let inspector = evm.inspector().unwrap().downcast_ref::().unwrap(); + + assert!(result.status); + assert_eq!(inspector.steps, 1); + assert_eq!(inspector.opcodes, [op::ADD]); + } + #[test] fn evm_transaction_inspects_eip7708_transfer_log() { let caller = Address::from([0xaa; 20]); diff --git a/crates/evm2/src/evm/mod.rs b/crates/evm2/src/evm/mod.rs index ba1c389a..e5a8f886 100644 --- a/crates/evm2/src/evm/mod.rs +++ b/crates/evm2/src/evm/mod.rs @@ -265,6 +265,12 @@ impl Evm { self.inspector.as_deref_mut() } + /// Requests that the inspector configuration is refreshed before the next interpreter run. + #[inline] + pub const fn request_inspector_reconfigure(&mut self) { + self.registered_inspector_config = None; + } + #[inline] fn inspect_log(&mut self, log: &Log) { if let Some(inspector) = &mut self.inspector { @@ -704,23 +710,27 @@ impl> Evm { let interpreter_ref = interpreter.as_mut(); interpreter_ref.init(bytecode, tx_env, message, caller_is_static); self.register_inspector(); - // SAFETY: `execution_config` points to a private field that host execution does not - // replace or mutate, so the pointee remains valid here. - let execution_config = unsafe { trustme::decouple_lt(&self.execution_config) }; self.inspect_initialize_interp(interpreter_ref); - let inspector = self.inspector.as_deref_mut().map(|inspector| { - // SAFETY: The inspector is stored in `self` and remains alive for the duration of the - // interpreter run. - unsafe { trustme::decouple_lt_mut(inspector) } - }); - let stop = if let Some(inspector) = inspector { - let inspector_config = self - .registered_inspector_config - .expect("inspector config must be registered before run_inspect"); - interpreter_ref.run_inspect(execution_config, &inspector_config, self, inspector) - } else { - interpreter_ref.run(execution_config, self) + self.register_inspector(); + let stop = { + // SAFETY: `execution_config` points to a private field that host execution does not + // replace or mutate, so the pointee remains valid here. + let execution_config = unsafe { trustme::decouple_lt(&self.execution_config) }; + let inspector = self.inspector.as_deref_mut().map(|inspector| { + // SAFETY: The inspector is stored in `self` and remains alive for the duration of + // the interpreter run. + unsafe { trustme::decouple_lt_mut(inspector) } + }); + if let Some(inspector) = inspector { + let inspector_config = self + .registered_inspector_config + .expect("inspector config must be registered before run_inspect"); + interpreter_ref.run_inspect(execution_config, &inspector_config, self, inspector) + } else { + interpreter_ref.run(execution_config, self) + } }; + self.register_inspector(); self.interpreter_pool.push(interpreter); stop @@ -870,6 +880,10 @@ impl> Host for Evm { } } + fn request_inspector_reconfigure(&mut self) { + Self::request_inspector_reconfigure(self); + } + fn selfdestruct( &mut self, contract: &Address, diff --git a/crates/evm2/src/interpreter/host.rs b/crates/evm2/src/interpreter/host.rs index 85964bdb..9dd294f4 100644 --- a/crates/evm2/src/interpreter/host.rs +++ b/crates/evm2/src/interpreter/host.rs @@ -145,6 +145,10 @@ pub trait Host { caller_is_static: bool, ) -> MessageResult; + /// Requests that the host refresh its registered inspector configuration. + #[inline] + fn request_inspector_reconfigure(&mut self) {} + /// Registers the current contract for self-destruction. fn selfdestruct( &mut self, diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index b938bcfe..92af2544 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -1,5 +1,5 @@ use super::{ - BytecodeRef, Gas, InstrStop, Memory, Message, MessageKind, MessageResult, Pc, Result, + BytecodeRef, Gas, Host, InstrStop, Memory, Message, MessageKind, MessageResult, Pc, Result, StackBacking, Word, }; use crate::{ @@ -162,6 +162,16 @@ impl<'frame, T: EvmTypes> Interpreter<'frame, T> { self.result = Err(stop); } + /// Requests that the host refresh the inspector configuration. + #[inline] + pub fn request_inspector_reconfigure(&mut self) { + if let Some(mut host) = self.host { + // SAFETY: `host` is initialized for the active run and the request only marks host + // inspector configuration dirty; it does not access interpreter-owned frame data. + unsafe { host.as_mut() }.request_inspector_reconfigure(); + } + } + /// Returns the current linear memory. #[inline] pub const fn memory_ref(&self) -> &Memory { From c1161bf030023c79f3ea4823e3951e37d53fe1db Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 07:30:36 +0200 Subject: [PATCH 09/17] chore: format merged inspector imports --- crates/evm2/src/evm/inspector.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 9e7d351a..5cfa541a 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -254,8 +254,7 @@ mod tests { use alloc::vec::Vec; use alloy_consensus::{TxLegacy, transaction::Recovered}; use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; - use core::marker::PhantomData; - use core::assert_matches; + use core::{assert_matches, marker::PhantomData}; #[derive(Default)] struct StepInspector { From 81e6a12cb69be352dbff67b57b972203bcc2a426 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 07:32:52 +0200 Subject: [PATCH 10/17] test(inspector): cover nested reconfigure --- crates/evm2/src/evm/inspector.rs | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 5cfa541a..383427b7 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -350,7 +350,9 @@ mod tests { impl Default for ReconfiguringInspector { fn default() -> Self { - Self { set: OpcodeSet::EMPTY, steps: 0, opcodes: Vec::new() } + let mut set = OpcodeSet::EMPTY; + set.insert(op::CALL); + Self { set, steps: 0, opcodes: Vec::new() } } } @@ -359,12 +361,11 @@ mod tests { InspectorConfig::new().with_opcode_set(self.set) } - fn initialize_interp(&mut self, interp: &mut Interpreter<'_, BaseEvmTypes>) { - self.set.insert(op::ADD); - interp.request_inspector_reconfigure(); - } - fn step(&mut self, interp: &mut Interpreter<'_, BaseEvmTypes>) { + if interp.opcode() == op::CALL { + self.set.insert(op::SLOAD); + interp.request_inspector_reconfigure(); + } self.steps += 1; self.opcodes.push(interp.opcode()); } @@ -1145,23 +1146,22 @@ mod tests { } #[test] - fn evm_transaction_reconfigures_inspector_from_initialize() { + fn evm_transaction_reconfigures_inspector_for_nested_frame() { let caller = Address::from([0xaa; 20]); let contract = Address::from([0xbb; 20]); - let code = Bytecode::new_legacy(Bytes::from_static(&[ - op::PUSH1, - 1, - op::PUSH1, - 2, - op::ADD, - op::STOP, - ])); + let child = Address::from([0xcc; 20]); + let mut parent_code = call_code(child); + parent_code.extend([op::CALL, op::STOP]); + let parent_code = Bytecode::new_legacy(Bytes::from(parent_code)); + let child_code = + Bytecode::new_legacy(Bytes::from_static(&[op::PUSH1, 0, op::SLOAD, op::STOP])); let mut database = InMemoryDB::default(); database.insert_account_info( &caller, AccountInfo::default().with_balance(U256::from(1_000_000_000_u64)), ); - database.insert_account_info(&contract, AccountInfo::default().with_code(code)); + database.insert_account_info(&contract, AccountInfo::default().with_code(parent_code)); + database.insert_account_info(&child, AccountInfo::default().with_code(child_code)); let mut evm = Evm::::new( SpecId::OSAKA, BlockEnv::default(), @@ -1179,8 +1179,8 @@ mod tests { let inspector = evm.inspector().unwrap().downcast_ref::().unwrap(); assert!(result.status); - assert_eq!(inspector.steps, 1); - assert_eq!(inspector.opcodes, [op::ADD]); + assert_eq!(inspector.steps, 2); + assert_eq!(inspector.opcodes, [op::CALL, op::SLOAD]); } #[test] From 7f3edbb29c0a93350d9222325ad37e71fa4f5b11 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 07:35:53 +0200 Subject: [PATCH 11/17] clean --- crates/evm2/src/evm/mod.rs | 7 +++---- crates/evm2/src/interpreter/host.rs | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/evm2/src/evm/mod.rs b/crates/evm2/src/evm/mod.rs index e5a8f886..65219fb0 100644 --- a/crates/evm2/src/evm/mod.rs +++ b/crates/evm2/src/evm/mod.rs @@ -261,7 +261,6 @@ impl Evm { /// Returns the active execution inspector mutably. #[inline] pub fn inspector_mut(&mut self) -> Option<&mut dyn Inspector> { - self.registered_inspector_config = None; self.inspector.as_deref_mut() } @@ -307,21 +306,21 @@ impl Evm { /// Sets the active execution inspector. #[inline] pub fn set_inspector + 'static>(&mut self, inspector: I) { - self.registered_inspector_config = None; + self.request_inspector_reconfigure(); self.inspector = Some(Box::new(inspector)); } /// Sets the active boxed execution inspector. #[inline] pub fn set_boxed_inspector(&mut self, inspector: Box>) { - self.registered_inspector_config = None; + self.request_inspector_reconfigure(); self.inspector = Some(inspector); } /// Removes the active execution inspector. #[inline] pub fn clear_inspector(&mut self) -> Option>> { - self.registered_inspector_config = None; + self.request_inspector_reconfigure(); self.inspector.take() } diff --git a/crates/evm2/src/interpreter/host.rs b/crates/evm2/src/interpreter/host.rs index 9dd294f4..4bdef745 100644 --- a/crates/evm2/src/interpreter/host.rs +++ b/crates/evm2/src/interpreter/host.rs @@ -145,10 +145,6 @@ pub trait Host { caller_is_static: bool, ) -> MessageResult; - /// Requests that the host refresh its registered inspector configuration. - #[inline] - fn request_inspector_reconfigure(&mut self) {} - /// Registers the current contract for self-destruction. fn selfdestruct( &mut self, @@ -156,4 +152,8 @@ pub trait Host { target: &Address, skip_cold_load: bool, ) -> Result; + + /// Requests that the host refresh its registered inspector configuration. + #[inline] + fn request_inspector_reconfigure(&mut self) {} } From 050a242abd8783e77330fc3c0f683646a8b49deb Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 07:41:31 +0200 Subject: [PATCH 12/17] test(inspector): model cheatcode reconfigure --- crates/evm2/src/evm/inspector.rs | 400 +++++++++++++------------ crates/evm2/src/interpreter/runtime.rs | 5 +- 2 files changed, 217 insertions(+), 188 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 383427b7..56186345 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -256,62 +256,6 @@ mod tests { use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; use core::{assert_matches, marker::PhantomData}; - #[derive(Default)] - struct StepInspector { - steps: usize, - step_ends: usize, - } - - impl Inspector for StepInspector { - fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { - self.steps += 1; - } - - fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { - self.step_ends += 1; - } - } - - struct StopOnStepInspector { - opcode: u8, - steps: usize, - step_ends: usize, - } - - impl Inspector for StopOnStepInspector { - fn step(&mut self, interp: &mut Interpreter<'_, TestTypes>) { - self.steps += 1; - if interp.opcode() == self.opcode { - interp.set_stop(InstrStop::Revert); - } - } - - fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { - self.step_ends += 1; - } - } - - struct StopOnStepEndInspector { - opcode: u8, - last_opcode: Option, - steps: usize, - step_ends: usize, - } - - impl Inspector for StopOnStepEndInspector { - fn step(&mut self, interp: &mut Interpreter<'_, TestTypes>) { - self.steps += 1; - self.last_opcode = Some(interp.opcode()); - } - - fn step_end(&mut self, interp: &mut Interpreter<'_, TestTypes>) { - self.step_ends += 1; - if self.last_opcode == Some(self.opcode) { - interp.set_stop(InstrStop::Revert); - } - } - } - struct OpcodeInterestInspector { steps: usize, step_ends: usize, @@ -342,35 +286,6 @@ mod tests { } } - struct ReconfiguringInspector { - set: OpcodeSet, - steps: usize, - opcodes: Vec, - } - - impl Default for ReconfiguringInspector { - fn default() -> Self { - let mut set = OpcodeSet::EMPTY; - set.insert(op::CALL); - Self { set, steps: 0, opcodes: Vec::new() } - } - } - - impl Inspector for ReconfiguringInspector { - fn config(&self) -> InspectorConfig { - InspectorConfig::new().with_opcode_set(self.set) - } - - fn step(&mut self, interp: &mut Interpreter<'_, BaseEvmTypes>) { - if interp.opcode() == op::CALL { - self.set.insert(op::SLOAD); - interp.request_inspector_reconfigure(); - } - self.steps += 1; - self.opcodes.push(interp.opcode()); - } - } - #[derive(Default)] struct MessageInspector { call_depth: Option, @@ -435,38 +350,6 @@ mod tests { } } - struct MutateCallInspector { - destination: Address, - } - - impl Inspector for MutateCallInspector { - fn call(&mut self, message: &mut Message) -> Option> { - message.destination = self.destination; - None - } - } - - struct CallEndInspector; - - impl Inspector for CallEndInspector { - fn call(&mut self, message: &mut Message) -> Option> { - Some(MessageResult { - stop: InstrStop::Revert, - gas: GasTracker::new(message.gas_limit), - ..Default::default() - }) - } - - fn call_end( - &mut self, - _message: &Message, - result: &mut MessageResult, - ) { - result.stop = InstrStop::Return; - result.output = Bytes::from_static(&[0xaa, 0xbb]); - } - } - struct OverrideCreateInspector { created: Address, create_depth: Option, @@ -493,29 +376,6 @@ mod tests { } } - struct CreateEndInspector { - created: Address, - } - - impl Inspector for CreateEndInspector { - fn create(&mut self, message: &mut Message) -> Option> { - Some(MessageResult { - stop: InstrStop::Revert, - gas: GasTracker::new(message.gas_limit), - ..Default::default() - }) - } - - fn create_end( - &mut self, - _message: &Message, - result: &mut MessageResult, - ) { - result.stop = InstrStop::Return; - result.created_address = Some(self.created); - } - } - #[derive(Default)] struct LogInspector { logs: Vec, @@ -527,48 +387,6 @@ mod tests { } } - #[derive(Default)] - struct EmptySetLogInspector { - steps: usize, - step_ends: usize, - logs: Vec, - } - - impl Inspector for EmptySetLogInspector { - fn config(&self) -> InspectorConfig { - InspectorConfig::new().with_opcode_set(OpcodeSet::EMPTY) - } - - fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { - self.steps += 1; - } - - fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { - self.step_ends += 1; - } - - fn log(&mut self, log: &Log) { - self.logs.push(log.clone()); - } - } - - #[derive(Default)] - struct FailingStepInspector { - steps: usize, - step_ends: usize, - } - - impl Inspector for FailingStepInspector { - fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { - self.steps += 1; - } - - fn step_end(&mut self, interp: &mut Interpreter<'_, TestTypes>) { - let _ = interp; - self.step_ends += 1; - } - } - #[derive(Default)] struct E2eState { initialized: usize, @@ -669,6 +487,22 @@ mod tests { #[test] fn inspect_run_steps() { + #[derive(Default)] + struct StepInspector { + steps: usize, + step_ends: usize, + } + + impl Inspector for StepInspector { + fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.steps += 1; + } + + fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.step_ends += 1; + } + } + let mut host = TestHost::default(); let mut inspector = StepInspector::default(); @@ -687,6 +521,25 @@ mod tests { #[test] fn step_can_stop_before_current_opcode_executes() { + struct StopOnStepInspector { + opcode: u8, + steps: usize, + step_ends: usize, + } + + impl Inspector for StopOnStepInspector { + fn step(&mut self, interp: &mut Interpreter<'_, TestTypes>) { + self.steps += 1; + if interp.opcode() == self.opcode { + interp.set_stop(InstrStop::Revert); + } + } + + fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.step_ends += 1; + } + } + let mut host = TestHost::default(); let mut inspector = StopOnStepInspector { opcode: op::ADD, steps: 0, step_ends: 0 }; @@ -706,6 +559,27 @@ mod tests { #[test] fn step_end_can_stop_before_next_opcode_executes() { + struct StopOnStepEndInspector { + opcode: u8, + last_opcode: Option, + steps: usize, + step_ends: usize, + } + + impl Inspector for StopOnStepEndInspector { + fn step(&mut self, interp: &mut Interpreter<'_, TestTypes>) { + self.steps += 1; + self.last_opcode = Some(interp.opcode()); + } + + fn step_end(&mut self, interp: &mut Interpreter<'_, TestTypes>) { + self.step_ends += 1; + if self.last_opcode == Some(self.opcode) { + interp.set_stop(InstrStop::Revert); + } + } + } + let mut host = TestHost::default(); let mut inspector = StopOnStepEndInspector { opcode: op::PUSH1, last_opcode: None, steps: 0, step_ends: 0 }; @@ -821,6 +695,20 @@ mod tests { #[test] fn call_inspector_can_mutate_message_before_host() { + struct MutateCallInspector { + destination: Address, + } + + impl Inspector for MutateCallInspector { + fn call( + &mut self, + message: &mut Message, + ) -> Option> { + message.destination = self.destination; + None + } + } + let target = Address::from([0x22; 20]); let replacement = Address::from([0x33; 20]); let mut host = TestHost::default(); @@ -839,6 +727,30 @@ mod tests { #[test] fn call_end_can_mutate_result_before_opcode_observes_it() { + struct CallEndInspector; + + impl Inspector for CallEndInspector { + fn call( + &mut self, + message: &mut Message, + ) -> Option> { + Some(MessageResult { + stop: InstrStop::Revert, + gas: GasTracker::new(message.gas_limit), + ..Default::default() + }) + } + + fn call_end( + &mut self, + _message: &Message, + result: &mut MessageResult, + ) { + result.stop = InstrStop::Return; + result.output = Bytes::from_static(&[0xaa, 0xbb]); + } + } + let target = Address::from([0x22; 20]); let mut host = TestHost::default(); let mut inspector = CallEndInspector; @@ -920,6 +832,32 @@ mod tests { #[test] fn create_end_can_mutate_result_before_opcode_observes_it() { + struct CreateEndInspector { + created: Address, + } + + impl Inspector for CreateEndInspector { + fn create( + &mut self, + message: &mut Message, + ) -> Option> { + Some(MessageResult { + stop: InstrStop::Revert, + gas: GasTracker::new(message.gas_limit), + ..Default::default() + }) + } + + fn create_end( + &mut self, + _message: &Message, + result: &mut MessageResult, + ) { + result.stop = InstrStop::Return; + result.created_address = Some(self.created); + } + } + let created = Address::from([0x88; 20]); let mut host = TestHost::default(); let mut inspector = CreateEndInspector { created }; @@ -957,6 +895,31 @@ mod tests { #[test] fn empty_opcode_set_skips_steps_but_keeps_other_hooks() { + #[derive(Default)] + struct EmptySetLogInspector { + steps: usize, + step_ends: usize, + logs: Vec, + } + + impl Inspector for EmptySetLogInspector { + fn config(&self) -> InspectorConfig { + InspectorConfig::new().with_opcode_set(OpcodeSet::EMPTY) + } + + fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.steps += 1; + } + + fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.step_ends += 1; + } + + fn log(&mut self, log: &Log) { + self.logs.push(log.clone()); + } + } + let contract = Address::from([0x11; 20]); let mut host = TestHost::default(); let mut inspector = EmptySetLogInspector::default(); @@ -993,6 +956,22 @@ mod tests { #[test] fn step_end_runs_for_failing_opcode_with_result_set() { + #[derive(Default)] + struct FailingStepInspector { + steps: usize, + step_ends: usize, + } + + impl Inspector for FailingStepInspector { + fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.steps += 1; + } + + fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + self.step_ends += 1; + } + } + let mut host = TestHost::default(); let mut inspector = FailingStepInspector::default(); @@ -1147,10 +1126,56 @@ mod tests { #[test] fn evm_transaction_reconfigures_inspector_for_nested_frame() { + const CHEATCODE_ADDRESS: Address = Address::repeat_byte(0x71); + + struct FakeCheatcodesInspector { + set: OpcodeSet, + steps: usize, + opcodes: Vec, + cheatcode_calls: usize, + } + + impl Default for FakeCheatcodesInspector { + fn default() -> Self { + let mut set = OpcodeSet::EMPTY; + set.insert(op::CALL); + Self { set, steps: 0, opcodes: Vec::new(), cheatcode_calls: 0 } + } + } + + impl Inspector for FakeCheatcodesInspector { + fn config(&self) -> InspectorConfig { + InspectorConfig::new().with_opcode_set(self.set) + } + + fn step(&mut self, interp: &mut Interpreter<'_, BaseEvmTypes>) { + self.steps += 1; + self.opcodes.push(interp.opcode()); + } + + fn call( + &mut self, + message: &mut Message, + ) -> Option> { + if message.destination != CHEATCODE_ADDRESS { + return None; + } + self.cheatcode_calls += 1; + self.set.insert(op::SLOAD); + Some(MessageResult { + stop: InstrStop::Return, + gas: GasTracker::new(message.gas_limit), + ..Default::default() + }) + } + } + let caller = Address::from([0xaa; 20]); let contract = Address::from([0xbb; 20]); let child = Address::from([0xcc; 20]); - let mut parent_code = call_code(child); + let mut parent_code = call_code(CHEATCODE_ADDRESS); + parent_code.push(op::CALL); + parent_code.extend(call_code(child)); parent_code.extend([op::CALL, op::STOP]); let parent_code = Bytecode::new_legacy(Bytes::from(parent_code)); let child_code = @@ -1169,18 +1194,19 @@ mod tests { database, Precompiles::base(SpecId::OSAKA), ); - evm.set_inspector(ReconfiguringInspector::default()); + evm.set_inspector(FakeCheatcodesInspector::default()); let tx = RecoveredTxEnvelope::Legacy(Recovered::new_unchecked( TxLegacy { to: TxKind::Call(contract), gas_limit: 100_000, ..Default::default() }, caller, )); let result = evm.transact(&tx).unwrap(); - let inspector = evm.inspector().unwrap().downcast_ref::().unwrap(); + let inspector = evm.inspector().unwrap().downcast_ref::().unwrap(); assert!(result.status); - assert_eq!(inspector.steps, 2); - assert_eq!(inspector.opcodes, [op::CALL, op::SLOAD]); + assert_eq!(inspector.cheatcode_calls, 1); + assert_eq!(inspector.steps, 3); + assert_eq!(inspector.opcodes, [op::CALL, op::CALL, op::SLOAD]); } #[test] diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index 92af2544..75f86c71 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -452,7 +452,10 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { #[inline] pub(crate) fn inspect_call(&mut self, message: &mut Message) -> Option> { - self.inspector().and_then(|inspector| inspector.call(message)) + let inspector = self.inspector()?; + let result = inspector.call(message); + self.host().request_inspector_reconfigure(); + result } #[inline] From c3ed7741afdebb7c6b617f1026528feca3774d2e Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 07:42:52 +0200 Subject: [PATCH 13/17] test(inspector): accept slices in push helper --- crates/evm2/src/evm/inspector.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 56186345..4fb67d46 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -436,9 +436,9 @@ mod tests { } } - fn push_all(code: &mut Vec, values: [Word; N]) { + fn push_all(code: &mut Vec, values: &[Word]) { for value in values { - push(code, value); + push(code, *value); } } @@ -466,7 +466,7 @@ mod tests { let mut code = Vec::new(); push_all( &mut code, - [ + &[ Word::ZERO, Word::ZERO, Word::ZERO, @@ -481,7 +481,7 @@ mod tests { fn create_code() -> Vec { let mut code = Vec::new(); - push_all(&mut code, [Word::ZERO, Word::ZERO, Word::ZERO]); + push_all(&mut code, &[Word::ZERO, Word::ZERO, Word::ZERO]); code } From b1382ef2dc1e01e908a454f4d56805a0829a3561 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 17:25:49 +0200 Subject: [PATCH 14/17] chore: re-register only when None --- crates/evm2/src/evm/config.rs | 5 ----- crates/evm2/src/evm/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/evm2/src/evm/config.rs b/crates/evm2/src/evm/config.rs index 1d4e816a..185d3a79 100644 --- a/crates/evm2/src/evm/config.rs +++ b/crates/evm2/src/evm/config.rs @@ -12,7 +12,6 @@ use crate::{ }; use alloc::boxed::Box; use core::fmt; -use derive_where::derive_where; /// Runtime EVM type family. /// @@ -122,14 +121,10 @@ pub struct ExecutionConfig { inner: Box>, } -#[derive_where(Debug)] struct ExecutionConfigInner { version: Version, - #[derive_where(skip)] instructions: &'static InstrTable, - #[derive_where(skip)] inspect_instructions: InstrTable, - #[derive_where(skip)] inspect_instruction_source: &'static InstrTable, } diff --git a/crates/evm2/src/evm/mod.rs b/crates/evm2/src/evm/mod.rs index 65219fb0..9806c55f 100644 --- a/crates/evm2/src/evm/mod.rs +++ b/crates/evm2/src/evm/mod.rs @@ -739,10 +739,10 @@ impl> Evm { let Some(inspector) = &self.inspector else { return; }; - let inspector_config = inspector.config(); - if self.registered_inspector_config == Some(inspector_config) { + if self.registered_inspector_config.is_some() { return; } + let inspector_config = inspector.config(); self.execution_config.register_inspector(&inspector_config); self.registered_inspector_config = Some(inspector_config); } From 62c3764a7e90171651209419ac5350432deabccc Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 17:41:01 +0200 Subject: [PATCH 15/17] refactor(inspector): pass interpreter to call hooks --- crates/evm2/examples/custom_evm/main.rs | 6 ++- crates/evm2/src/evm/inspector.rs | 67 ++++++++++++++++++++++--- crates/evm2/src/interpreter/runtime.rs | 17 +++---- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/crates/evm2/examples/custom_evm/main.rs b/crates/evm2/examples/custom_evm/main.rs index 5b1d4c08..bef9da78 100644 --- a/crates/evm2/examples/custom_evm/main.rs +++ b/crates/evm2/examples/custom_evm/main.rs @@ -226,7 +226,11 @@ impl Inspector for ExampleInspector { self.state.logs += 1; } - fn call(&mut self, _message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, CustomTypes>, + _message: &mut Message, + ) -> Option> { self.state.calls += 1; None } diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 4fb67d46..4f1cd143 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -183,28 +183,50 @@ pub trait Inspector: Any + Send { /// Called before a call message executes. #[inline] - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &mut Message, + ) -> Option> { + let _ = interp; let _ = message; None } /// Called after a call message executes. #[inline] - fn call_end(&mut self, message: &Message, result: &mut MessageResult) { + fn call_end( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &Message, + result: &mut MessageResult, + ) { + let _ = interp; let _ = message; let _ = result; } /// Called before a create message executes. #[inline] - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &mut Message, + ) -> Option> { + let _ = interp; let _ = message; None } /// Called after a create message executes. #[inline] - fn create_end(&mut self, message: &Message, result: &mut MessageResult) { + fn create_end( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &Message, + result: &mut MessageResult, + ) { + let _ = interp; let _ = message; let _ = result; } @@ -296,26 +318,36 @@ mod tests { } impl Inspector for MessageInspector { - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.call_depth = Some(message.depth); None } fn call_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { self.call_end_stop = Some(result.stop); } - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.create_depth = Some(message.depth); None } fn create_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -334,7 +366,11 @@ mod tests { } impl Inspector for OverrideCallInspector { - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.call_depth = Some(message.depth); let mut result = self.result.clone(); result.gas.set_remaining(message.gas_limit); @@ -343,6 +379,7 @@ mod tests { fn call_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -357,7 +394,11 @@ mod tests { } impl Inspector for OverrideCreateInspector { - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.create_depth = Some(message.depth); Some(MessageResult { stop: InstrStop::Return, @@ -369,6 +410,7 @@ mod tests { fn create_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -421,6 +463,7 @@ mod tests { fn call( &mut self, + _interp: &mut Interpreter<'_, BaseEvmTypes>, _message: &mut Message, ) -> Option> { self.state.calls += 1; @@ -429,6 +472,7 @@ mod tests { fn create( &mut self, + _interp: &mut Interpreter<'_, BaseEvmTypes>, _message: &mut Message, ) -> Option> { self.state.creates += 1; @@ -702,6 +746,7 @@ mod tests { impl Inspector for MutateCallInspector { fn call( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, message: &mut Message, ) -> Option> { message.destination = self.destination; @@ -732,6 +777,7 @@ mod tests { impl Inspector for CallEndInspector { fn call( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, message: &mut Message, ) -> Option> { Some(MessageResult { @@ -743,6 +789,7 @@ mod tests { fn call_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -839,6 +886,7 @@ mod tests { impl Inspector for CreateEndInspector { fn create( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, message: &mut Message, ) -> Option> { Some(MessageResult { @@ -850,6 +898,7 @@ mod tests { fn create_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -1155,6 +1204,7 @@ mod tests { fn call( &mut self, + interp: &mut Interpreter<'_, BaseEvmTypes>, message: &mut Message, ) -> Option> { if message.destination != CHEATCODE_ADDRESS { @@ -1162,6 +1212,7 @@ mod tests { } self.cheatcode_calls += 1; self.set.insert(op::SLOAD); + interp.request_inspector_reconfigure(); Some(MessageResult { stop: InstrStop::Return, gas: GasTracker::new(message.gas_limit), diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index 75f86c71..29eb800c 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -452,22 +452,21 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { #[inline] pub(crate) fn inspect_call(&mut self, message: &mut Message) -> Option> { - let inspector = self.inspector()?; - let result = inspector.call(message); - self.host().request_inspector_reconfigure(); - result + let mut inspector = self.0.inspector?; + unsafe { inspector.as_mut() }.call(&mut self.0, message) } #[inline] pub(crate) fn inspect_call_end(&mut self, message: &Message, result: &mut MessageResult) { - if let Some(inspector) = self.inspector() { - inspector.call_end(message, result); + if let Some(mut inspector) = self.0.inspector { + unsafe { inspector.as_mut() }.call_end(&mut self.0, message, result); } } #[inline] pub(crate) fn inspect_create(&mut self, message: &mut Message) -> Option> { - self.inspector().and_then(|inspector| inspector.create(message)) + let mut inspector = self.0.inspector?; + unsafe { inspector.as_mut() }.create(&mut self.0, message) } #[inline] @@ -476,8 +475,8 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { message: &Message, result: &mut MessageResult, ) { - if let Some(inspector) = self.inspector() { - inspector.create_end(message, result); + if let Some(mut inspector) = self.0.inspector { + unsafe { inspector.as_mut() }.create_end(&mut self.0, message, result); } } From 435368e2fef20e8f693f40a9e5ef094415c1bf64 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 May 2026 17:47:50 +0200 Subject: [PATCH 16/17] refactor(inspector): pass interpreter to call hooks --- crates/evm2/examples/custom_evm/main.rs | 6 +- crates/evm2/src/evm/inspector.rs | 92 ++++++++++++++++++++++--- crates/evm2/src/interpreter/runtime.rs | 14 ++-- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/crates/evm2/examples/custom_evm/main.rs b/crates/evm2/examples/custom_evm/main.rs index 5b1d4c08..bef9da78 100644 --- a/crates/evm2/examples/custom_evm/main.rs +++ b/crates/evm2/examples/custom_evm/main.rs @@ -226,7 +226,11 @@ impl Inspector for ExampleInspector { self.state.logs += 1; } - fn call(&mut self, _message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, CustomTypes>, + _message: &mut Message, + ) -> Option> { self.state.calls += 1; None } diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index d9de8f6c..87102fe7 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -35,28 +35,50 @@ pub trait Inspector: Any + Send { /// Called before a call message executes. #[inline] - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &mut Message, + ) -> Option> { + let _ = interp; let _ = message; None } /// Called after a call message executes. #[inline] - fn call_end(&mut self, message: &Message, result: &mut MessageResult) { + fn call_end( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &Message, + result: &mut MessageResult, + ) { + let _ = interp; let _ = message; let _ = result; } /// Called before a create message executes. #[inline] - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &mut Message, + ) -> Option> { + let _ = interp; let _ = message; None } /// Called after a create message executes. #[inline] - fn create_end(&mut self, message: &Message, result: &mut MessageResult) { + fn create_end( + &mut self, + interp: &mut Interpreter<'_, T>, + message: &Message, + result: &mut MessageResult, + ) { + let _ = interp; let _ = message; let _ = result; } @@ -167,36 +189,54 @@ mod tests { #[derive(Default)] struct MessageInspector { call_depth: Option, + call_opcode: Option, + call_end_opcode: Option, call_end_stop: Option, create_depth: Option, + create_opcode: Option, + create_end_opcode: Option, create_end_stop: Option, selfdestruct: Option<(Address, Address, Word)>, } impl Inspector for MessageInspector { - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.call_depth = Some(message.depth); + self.call_opcode = Some(interp.opcode()); None } fn call_end( &mut self, + interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { + self.call_end_opcode = Some(interp.opcode()); self.call_end_stop = Some(result.stop); } - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.create_depth = Some(message.depth); + self.create_opcode = Some(interp.opcode()); None } fn create_end( &mut self, + interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { + self.create_end_opcode = Some(interp.opcode()); self.create_end_stop = Some(result.stop); } @@ -212,7 +252,11 @@ mod tests { } impl Inspector for OverrideCallInspector { - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.call_depth = Some(message.depth); let mut result = self.result.clone(); result.gas.set_remaining(message.gas_limit); @@ -221,6 +265,7 @@ mod tests { fn call_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -233,7 +278,11 @@ mod tests { } impl Inspector for MutateCallInspector { - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { message.destination = self.destination; None } @@ -242,7 +291,11 @@ mod tests { struct CallEndInspector; impl Inspector for CallEndInspector { - fn call(&mut self, message: &mut Message) -> Option> { + fn call( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { Some(MessageResult { stop: InstrStop::Revert, gas: GasTracker::new(message.gas_limit), @@ -252,6 +305,7 @@ mod tests { fn call_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -267,7 +321,11 @@ mod tests { } impl Inspector for OverrideCreateInspector { - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { self.create_depth = Some(message.depth); Some(MessageResult { stop: InstrStop::Return, @@ -279,6 +337,7 @@ mod tests { fn create_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -291,7 +350,11 @@ mod tests { } impl Inspector for CreateEndInspector { - fn create(&mut self, message: &mut Message) -> Option> { + fn create( + &mut self, + _interp: &mut Interpreter<'_, TestTypes>, + message: &mut Message, + ) -> Option> { Some(MessageResult { stop: InstrStop::Revert, gas: GasTracker::new(message.gas_limit), @@ -301,6 +364,7 @@ mod tests { fn create_end( &mut self, + _interp: &mut Interpreter<'_, TestTypes>, _message: &Message, result: &mut MessageResult, ) { @@ -371,6 +435,7 @@ mod tests { fn call( &mut self, + _interp: &mut Interpreter<'_, BaseEvmTypes>, _message: &mut Message, ) -> Option> { self.state.calls += 1; @@ -379,6 +444,7 @@ mod tests { fn create( &mut self, + _interp: &mut Interpreter<'_, BaseEvmTypes>, _message: &mut Message, ) -> Option> { self.state.creates += 1; @@ -509,6 +575,8 @@ mod tests { assert_matches!(stop, InstrStop::Stop); assert_eq!(stack, [Word::ZERO]); assert_eq!(inspector.call_depth, Some(CALL_DEPTH_LIMIT + 1)); + assert_eq!(inspector.call_opcode, Some(op::CALL)); + assert_eq!(inspector.call_end_opcode, Some(op::CALL)); assert_eq!(inspector.call_end_stop, Some(InstrStop::CallTooDeep)); assert!(host.calls.is_empty()); } @@ -617,6 +685,8 @@ mod tests { assert_matches!(stop, InstrStop::Stop); assert_eq!(stack, [Word::ZERO]); assert_eq!(inspector.create_depth, Some(CALL_DEPTH_LIMIT + 1)); + assert_eq!(inspector.create_opcode, Some(op::CREATE)); + assert_eq!(inspector.create_end_opcode, Some(op::CREATE)); assert_eq!(inspector.create_end_stop, Some(InstrStop::CallTooDeep)); assert!(host.calls.is_empty()); } diff --git a/crates/evm2/src/interpreter/runtime.rs b/crates/evm2/src/interpreter/runtime.rs index b1b1db55..32785f6f 100644 --- a/crates/evm2/src/interpreter/runtime.rs +++ b/crates/evm2/src/interpreter/runtime.rs @@ -389,19 +389,21 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { #[inline] pub(crate) fn inspect_call(&mut self, message: &mut Message) -> Option> { - self.inspector().and_then(|inspector| inspector.call(message)) + let mut inspector = self.0.inspector?; + unsafe { inspector.as_mut() }.call(&mut self.0, message) } #[inline] pub(crate) fn inspect_call_end(&mut self, message: &Message, result: &mut MessageResult) { - if let Some(inspector) = self.inspector() { - inspector.call_end(message, result); + if let Some(mut inspector) = self.0.inspector { + unsafe { inspector.as_mut() }.call_end(&mut self.0, message, result); } } #[inline] pub(crate) fn inspect_create(&mut self, message: &mut Message) -> Option> { - self.inspector().and_then(|inspector| inspector.create(message)) + let mut inspector = self.0.inspector?; + unsafe { inspector.as_mut() }.create(&mut self.0, message) } #[inline] @@ -410,8 +412,8 @@ impl<'frame, T: EvmTypes> InterpreterState<'frame, T> { message: &Message, result: &mut MessageResult, ) { - if let Some(inspector) = self.inspector() { - inspector.create_end(message, result); + if let Some(mut inspector) = self.0.inspector { + unsafe { inspector.as_mut() }.create_end(&mut self.0, message, result); } } From 0c7291b3c4500b5b01b259fdda47cd37cc2e85d0 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 27 Jun 2026 20:32:30 +0200 Subject: [PATCH 17/17] test: update inspector opcode config tests --- crates/evm2/src/evm/inspector.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/evm2/src/evm/inspector.rs b/crates/evm2/src/evm/inspector.rs index 727e113c..278213cf 100644 --- a/crates/evm2/src/evm/inspector.rs +++ b/crates/evm2/src/evm/inspector.rs @@ -520,7 +520,7 @@ mod tests { struct HookInspector { call_depths: Vec, call_opcode: Option, - call_end_opcode: Option, + call_end_opcodes: Vec, call_end_stops: Vec, create_depths: Vec, create_opcode: Option, @@ -546,7 +546,7 @@ mod tests { _message: &Message, result: &mut MessageResult, ) { - self.call_end_opcode = Some(interp.opcode()); + self.call_end_opcodes.push(interp.opcode()); self.call_end_stops.push(result.stop); } @@ -751,7 +751,7 @@ mod tests { assert_matches!(result.stop, InstrStop::Stop); assert_eq!(inspector.call_depths, [CALL_DEPTH_LIMIT, CALL_DEPTH_LIMIT + 1]); assert_eq!(inspector.call_opcode, Some(op::CALL)); - assert_eq!(inspector.call_end_opcode, Some(op::CALL)); + assert_eq!(inspector.call_end_opcodes, [op::CALL, op::PUSH1]); assert_eq!(inspector.call_end_stops, [InstrStop::CallTooDeep, InstrStop::Stop]); } @@ -1072,43 +1072,40 @@ mod tests { logs: Vec, } - impl Inspector for EmptySetLogInspector { + impl Inspector for EmptySetLogInspector { fn config(&self) -> InspectorConfig { InspectorConfig::new().with_opcode_set(OpcodeSet::EMPTY) } - fn step(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + fn step(&mut self, _interp: &mut Interpreter<'_, BaseEvmTypes>) { self.steps += 1; } - fn step_end(&mut self, _interp: &mut Interpreter<'_, TestTypes>) { + fn step_end(&mut self, _interp: &mut Interpreter<'_, BaseEvmTypes>) { self.step_ends += 1; } - fn log(&mut self, log: &Log, _host: &mut TestHost) { + fn log(&mut self, log: &Log, _host: &mut Evm) { self.logs.push(log.clone()); } } let contract = Address::from([0x11; 20]); - let mut host = TestHost::default(); - let mut inspector = EmptySetLogInspector::default(); let code = Vec::from([op::PUSH1, 0, op::PUSH1, 0, op::LOG0, op::STOP]); - let (stop, _) = run_with_inspector( + let (result, inspector, evm) = run_evm_with_inspector( code, - &mut host, &Message { destination: contract, ..Default::default() }, 10_000, - &mut inspector, + EmptySetLogInspector::default(), ); - assert!(matches!(stop, InstrStop::Stop)); + assert!(matches!(result.stop, InstrStop::Stop)); assert_eq!(inspector.steps, 0); assert_eq!(inspector.step_ends, 0); assert_eq!(inspector.logs.len(), 1); assert_eq!(inspector.logs[0].address, contract); - assert_eq!(host.logs, inspector.logs); + assert_eq!(evm.logs(), inspector.logs); } #[test]