diff --git a/bins/revme/src/cmd/bench/snailtracer.rs b/bins/revme/src/cmd/bench/snailtracer.rs index 631ea54163..0228b3df4c 100644 --- a/bins/revme/src/cmd/bench/snailtracer.rs +++ b/bins/revme/src/cmd/bench/snailtracer.rs @@ -1,10 +1,11 @@ use context::TxEnv; use criterion::Criterion; use database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET}; +use inspector::CountInspector; use revm::{ bytecode::Bytecode, primitives::{bytes, hex, Bytes, TxKind}, - Context, ExecuteEvm, MainBuilder, MainContext, + Context, ExecuteEvm, InspectEvm, MainBuilder, MainContext, }; pub fn run(criterion: &mut Criterion) { @@ -13,7 +14,8 @@ pub fn run(criterion: &mut Criterion) { let mut evm = Context::mainnet() .with_db(BenchmarkDB::new_bytecode(bytecode.clone())) .modify_cfg_chained(|c| c.disable_nonce_check = true) - .build_mainnet(); + .build_mainnet() + .with_inspector(CountInspector::new()); let tx = TxEnv::builder() .caller(BENCH_CALLER) @@ -35,6 +37,19 @@ pub fn run(criterion: &mut Criterion) { criterion::BatchSize::SmallInput, ); }); + + criterion.bench_function("analysis-inspector", |b| { + b.iter_batched( + || { + // create a transaction input + tx.clone() + }, + |input| { + let _ = evm.inspect_one_tx(input); + }, + criterion::BatchSize::SmallInput, + ); + }); } const BYTES: &str = include_str!("snailtracer.hex"); diff --git a/crates/inspector/src/count_inspector.rs b/crates/inspector/src/count_inspector.rs new file mode 100644 index 0000000000..01c7967d4a --- /dev/null +++ b/crates/inspector/src/count_inspector.rs @@ -0,0 +1,337 @@ +//! CountInspector - Inspector that counts all opcodes that were called. +use crate::inspector::Inspector; +use interpreter::{interpreter_types::Jumps, InterpreterTypes}; +use primitives::HashMap; + +/// Inspector that counts all opcodes that were called during execution. +#[derive(Clone, Debug, Default)] +pub struct CountInspector { + /// Map from opcode value to count of times it was executed. + opcode_counts: HashMap, + /// Count of initialize_interp calls. + initialize_interp_count: u64, + /// Count of step calls. + step_count: u64, + /// Count of step_end calls. + step_end_count: u64, + /// Count of log calls. + log_count: u64, + /// Count of call calls. + call_count: u64, + /// Count of call_end calls. + call_end_count: u64, + /// Count of create calls. + create_count: u64, + /// Count of create_end calls. + create_end_count: u64, + /// Count of selfdestruct calls. + selfdestruct_count: u64, +} + +impl CountInspector { + /// Create a new CountInspector. + pub fn new() -> Self { + Self { + opcode_counts: HashMap::new(), + initialize_interp_count: 0, + step_count: 0, + step_end_count: 0, + log_count: 0, + call_count: 0, + call_end_count: 0, + create_count: 0, + create_end_count: 0, + selfdestruct_count: 0, + } + } + + /// Get the count for a specific opcode. + pub fn get_count(&self, opcode: u8) -> u64 { + self.opcode_counts.get(&opcode).copied().unwrap_or(0) + } + + /// Get a reference to all opcode counts. + pub fn opcode_counts(&self) -> &HashMap { + &self.opcode_counts + } + + /// Get the total number of opcodes executed. + pub fn total_opcodes(&self) -> u64 { + self.opcode_counts.values().sum() + } + + /// Get the number of unique opcodes executed. + pub fn unique_opcodes(&self) -> usize { + self.opcode_counts.len() + } + + /// Clear all counts. + pub fn clear(&mut self) { + self.opcode_counts.clear(); + self.initialize_interp_count = 0; + self.step_count = 0; + self.step_end_count = 0; + self.log_count = 0; + self.call_count = 0; + self.call_end_count = 0; + self.create_count = 0; + self.create_end_count = 0; + self.selfdestruct_count = 0; + } + + /// Get the count of initialize_interp calls. + pub fn initialize_interp_count(&self) -> u64 { + self.initialize_interp_count + } + + /// Get the count of step calls. + pub fn step_count(&self) -> u64 { + self.step_count + } + + /// Get the count of step_end calls. + pub fn step_end_count(&self) -> u64 { + self.step_end_count + } + + /// Get the count of log calls. + pub fn log_count(&self) -> u64 { + self.log_count + } + + /// Get the count of call calls. + pub fn call_count(&self) -> u64 { + self.call_count + } + + /// Get the count of call_end calls. + pub fn call_end_count(&self) -> u64 { + self.call_end_count + } + + /// Get the count of create calls. + pub fn create_count(&self) -> u64 { + self.create_count + } + + /// Get the count of create_end calls. + pub fn create_end_count(&self) -> u64 { + self.create_end_count + } + + /// Get the count of selfdestruct calls. + pub fn selfdestruct_count(&self) -> u64 { + self.selfdestruct_count + } +} + +impl Inspector for CountInspector { + fn initialize_interp( + &mut self, + _interp: &mut interpreter::Interpreter, + _context: &mut CTX, + ) { + self.initialize_interp_count += 1; + } + + fn step(&mut self, interp: &mut interpreter::Interpreter, _context: &mut CTX) { + self.step_count += 1; + let opcode = interp.bytecode.opcode(); + *self.opcode_counts.entry(opcode).or_insert(0) += 1; + } + + fn step_end(&mut self, _interp: &mut interpreter::Interpreter, _context: &mut CTX) { + self.step_end_count += 1; + } + + fn log( + &mut self, + _interp: &mut interpreter::Interpreter, + _context: &mut CTX, + _log: primitives::Log, + ) { + self.log_count += 1; + } + + fn call( + &mut self, + _context: &mut CTX, + _inputs: &mut interpreter::CallInputs, + ) -> Option { + self.call_count += 1; + None + } + + fn call_end( + &mut self, + _context: &mut CTX, + _inputs: &interpreter::CallInputs, + _outcome: &mut interpreter::CallOutcome, + ) { + self.call_end_count += 1; + } + + fn create( + &mut self, + _context: &mut CTX, + _inputs: &mut interpreter::CreateInputs, + ) -> Option { + self.create_count += 1; + None + } + + fn create_end( + &mut self, + _context: &mut CTX, + _inputs: &interpreter::CreateInputs, + _outcome: &mut interpreter::CreateOutcome, + ) { + self.create_end_count += 1; + } + + fn selfdestruct( + &mut self, + _contract: primitives::Address, + _target: primitives::Address, + _value: primitives::U256, + ) { + self.selfdestruct_count += 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::InspectEvm; + use context::Context; + use database::BenchmarkDB; + use handler::{MainBuilder, MainContext}; + use primitives::{Bytes, TxKind}; + use state::bytecode::{opcode, Bytecode}; + + #[test] + fn test_count_inspector() { + // Create simple bytecode that just adds two numbers and stops + let contract_data: Bytes = Bytes::from(vec![ + opcode::PUSH1, + 0x10, // 0: PUSH1 16 + opcode::PUSH1, + 0x20, // 2: PUSH1 32 + opcode::ADD, // 4: ADD + opcode::DUP1, // 5: DUP1 (duplicate the result) + opcode::PUSH1, + 0x00, // 6: PUSH1 0 + opcode::MSTORE, // 8: MSTORE (store result in memory) + opcode::STOP, // 9: STOP + ]); + let bytecode = Bytecode::new_raw(contract_data); + + let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode.clone())); + let mut count_inspector = CountInspector::new(); + + let mut evm = ctx.build_mainnet_with_inspector(&mut count_inspector); + + // Execute the contract + evm.inspect_one_tx( + context::TxEnv::builder() + .kind(TxKind::Call(database::BENCH_TARGET)) + .gas_limit(30000) + .build() + .unwrap(), + ) + .unwrap(); + + // Check opcode counts + assert_eq!(count_inspector.get_count(opcode::PUSH1), 3); + assert_eq!(count_inspector.get_count(opcode::ADD), 1); + assert_eq!(count_inspector.get_count(opcode::DUP1), 1); + assert_eq!(count_inspector.get_count(opcode::MSTORE), 1); + assert_eq!(count_inspector.get_count(opcode::STOP), 1); + + // Check totals + assert_eq!(count_inspector.total_opcodes(), 7); + assert_eq!(count_inspector.unique_opcodes(), 5); + + // Check inspector function counts + assert_eq!(count_inspector.initialize_interp_count(), 1); + assert_eq!(count_inspector.step_count(), 7); // Each opcode triggers a step + assert_eq!(count_inspector.step_end_count(), 7); // Each opcode triggers a step_end + assert_eq!(count_inspector.log_count(), 0); // No LOG opcodes + assert_eq!(count_inspector.call_count(), 1); // The transaction itself is a call + assert_eq!(count_inspector.call_end_count(), 1); + assert_eq!(count_inspector.create_count(), 0); // No CREATE opcodes + assert_eq!(count_inspector.create_end_count(), 0); + assert_eq!(count_inspector.selfdestruct_count(), 0); // No SELFDESTRUCT opcodes + } + + #[test] + fn test_count_inspector_clear() { + let mut inspector = CountInspector::new(); + + // Add some counts manually for testing + *inspector.opcode_counts.entry(opcode::PUSH1).or_insert(0) += 5; + *inspector.opcode_counts.entry(opcode::ADD).or_insert(0) += 3; + inspector.initialize_interp_count = 2; + inspector.step_count = 10; + inspector.step_end_count = 10; + inspector.log_count = 1; + inspector.call_count = 3; + inspector.call_end_count = 3; + inspector.create_count = 1; + inspector.create_end_count = 1; + inspector.selfdestruct_count = 1; + + assert_eq!(inspector.total_opcodes(), 8); + assert_eq!(inspector.unique_opcodes(), 2); + assert_eq!(inspector.initialize_interp_count(), 2); + assert_eq!(inspector.step_count(), 10); + + // Clear and verify + inspector.clear(); + assert_eq!(inspector.total_opcodes(), 0); + assert_eq!(inspector.unique_opcodes(), 0); + assert!(inspector.opcode_counts().is_empty()); + assert_eq!(inspector.initialize_interp_count(), 0); + assert_eq!(inspector.step_count(), 0); + assert_eq!(inspector.step_end_count(), 0); + assert_eq!(inspector.log_count(), 0); + assert_eq!(inspector.call_count(), 0); + assert_eq!(inspector.call_end_count(), 0); + assert_eq!(inspector.create_count(), 0); + assert_eq!(inspector.create_end_count(), 0); + assert_eq!(inspector.selfdestruct_count(), 0); + } + + #[test] + fn test_count_inspector_with_logs() { + // Create bytecode that emits a log + let contract_data: Bytes = Bytes::from(vec![ + opcode::PUSH1, + 0x20, // 0: PUSH1 32 (length) + opcode::PUSH1, + 0x00, // 2: PUSH1 0 (offset) + opcode::LOG0, // 4: LOG0 - emit log with no topics + opcode::STOP, // 5: STOP + ]); + let bytecode = Bytecode::new_raw(contract_data); + + let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode.clone())); + let mut count_inspector = CountInspector::new(); + + let mut evm = ctx.build_mainnet_with_inspector(&mut count_inspector); + + // Execute the contract + evm.inspect_one_tx( + context::TxEnv::builder() + .kind(TxKind::Call(database::BENCH_TARGET)) + .gas_limit(30000) + .build() + .unwrap(), + ) + .unwrap(); + + // Check that log was counted + assert_eq!(count_inspector.log_count(), 1); + assert_eq!(count_inspector.step_count(), 4); // 2 PUSH1 + LOG0 + STOP + } +} diff --git a/crates/inspector/src/lib.rs b/crates/inspector/src/lib.rs index 8740614ec1..aa62edda6f 100644 --- a/crates/inspector/src/lib.rs +++ b/crates/inspector/src/lib.rs @@ -5,6 +5,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(not(feature = "std"), no_std)] +mod count_inspector; #[cfg(all(feature = "std", feature = "serde-json"))] mod eip3155; mod either; @@ -27,6 +28,7 @@ pub mod inspectors { pub use super::gas::GasInspector; } +pub use count_inspector::CountInspector; pub use handler::{inspect_instructions, InspectorHandler}; pub use inspect::{InspectCommitEvm, InspectEvm}; pub use inspector::*;