Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions bins/revme/src/cmd/bench/snailtracer.rs
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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)
Expand All @@ -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");
337 changes: 337 additions & 0 deletions crates/inspector/src/count_inspector.rs
Original file line number Diff line number Diff line change
@@ -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<u8, u64>,
/// 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<u8, u64> {
&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<CTX, INTR: InterpreterTypes> Inspector<CTX, INTR> for CountInspector {
fn initialize_interp(
&mut self,
_interp: &mut interpreter::Interpreter<INTR>,
_context: &mut CTX,
) {
self.initialize_interp_count += 1;
}

fn step(&mut self, interp: &mut interpreter::Interpreter<INTR>, _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<INTR>, _context: &mut CTX) {
self.step_end_count += 1;
}

fn log(
&mut self,
_interp: &mut interpreter::Interpreter<INTR>,
_context: &mut CTX,
_log: primitives::Log,
) {
self.log_count += 1;
}

fn call(
&mut self,
_context: &mut CTX,
_inputs: &mut interpreter::CallInputs,
) -> Option<interpreter::CallOutcome> {
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<interpreter::CreateOutcome> {
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
}
}
2 changes: 2 additions & 0 deletions crates/inspector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
Expand Down
Loading