#[cfg(test)]
mod tests {
use crate::{InspectEvm, InspectSystemCallEvm, InspectorEvent, TestInspector};
use context::{Context, TxEnv};
use database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET};
use handler::{MainBuilder, MainContext};
use primitives::{address, Address, Bytes, TxKind, U256};
use state::{bytecode::opcode, AccountInfo, Bytecode};
#[test]
fn test_push_opcodes_and_stack_operations() {
let code = Bytes::from(vec![
opcode::PUSH1,
0x42,
opcode::PUSH2,
0x12,
0x34,
opcode::ADD,
opcode::PUSH1,
0x00,
opcode::MSTORE,
opcode::STOP,
]);
let bytecode = Bytecode::new_raw(code);
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let step_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Step(record) = e {
Some(record)
} else {
None
}
})
.collect();
let push1_event = &step_events[0];
assert_eq!(push1_event.opcode_name, "PUSH1");
assert_eq!(push1_event.before.stack_len, 0);
assert_eq!(push1_event.after.as_ref().unwrap().stack_len, 1);
let push2_event = &step_events[1];
assert_eq!(push2_event.opcode_name, "PUSH2");
assert_eq!(push2_event.before.stack_len, 1);
assert_eq!(push2_event.after.as_ref().unwrap().stack_len, 2);
let add_event = &step_events[2];
assert_eq!(add_event.opcode_name, "ADD");
assert_eq!(add_event.before.stack_len, 2);
assert_eq!(add_event.after.as_ref().unwrap().stack_len, 1);
assert!(inspector.get_step_count() >= 5); }
#[test]
fn test_jump_and_jumpi_control_flow() {
let code = Bytes::from(vec![
opcode::PUSH1,
0x08,
opcode::JUMP,
opcode::INVALID,
opcode::INVALID,
opcode::INVALID,
opcode::INVALID,
opcode::INVALID,
opcode::JUMPDEST, opcode::PUSH1,
0x01,
opcode::PUSH1,
0x0F,
opcode::JUMPI,
opcode::INVALID,
opcode::JUMPDEST, opcode::STOP,
]);
let bytecode = Bytecode::new_raw(code);
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let step_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Step(record) = e {
Some(record)
} else {
None
}
})
.collect();
let jump_event = step_events
.iter()
.find(|e| e.opcode_name == "JUMP")
.unwrap();
assert_eq!(jump_event.before.pc, 2); assert_eq!(jump_event.after.as_ref().unwrap().pc, 8);
let jumpi_event = step_events
.iter()
.find(|e| e.opcode_name == "JUMPI")
.unwrap();
assert!(jumpi_event.before.stack_len >= 2); assert_eq!(jumpi_event.after.as_ref().unwrap().pc, 0x0F);
}
#[test]
fn test_call_operations() {
let callee_code = Bytes::from(vec![
opcode::PUSH1,
0x42, opcode::PUSH1,
0x00, opcode::MSTORE,
opcode::PUSH1,
0x20, opcode::PUSH1,
0x00, opcode::RETURN,
]);
let caller_code = Bytes::from(vec![
opcode::PUSH1,
0x20, opcode::PUSH1,
0x00, opcode::PUSH1,
0x00, opcode::PUSH1,
0x00, opcode::PUSH1,
0x00, opcode::PUSH20,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
opcode::PUSH2,
0xFF,
0xFF, opcode::CALL,
opcode::STOP,
]);
let mut db = database::InMemoryDB::default();
db.insert_account_info(
BENCH_TARGET,
AccountInfo {
balance: U256::from(1_000_000_000_000_000_000u64),
nonce: 0,
code_hash: primitives::keccak256(&caller_code),
code: Some(Bytecode::new_raw(caller_code)),
..Default::default()
},
);
let callee_address = Address::new([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
]);
db.insert_account_info(
callee_address,
AccountInfo {
balance: U256::ZERO,
nonce: 0,
code_hash: primitives::keccak256(&callee_code),
code: Some(Bytecode::new_raw(callee_code)),
..Default::default()
},
);
let ctx = Context::mainnet().with_db(db);
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let call_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Call { inputs, outcome } = e {
Some((inputs, outcome))
} else {
None
}
})
.collect();
assert!(!call_events.is_empty(), "Should have recorded CALL events");
let (call_inputs, call_outcome) = &call_events[0];
assert_eq!(call_inputs.target_address, BENCH_TARGET);
assert!(call_outcome.is_some(), "Call should have completed");
}
#[test]
fn test_create_opcodes() {
let init_code = vec![
opcode::PUSH1,
0x42, opcode::PUSH1,
0x00, opcode::MSTORE,
opcode::PUSH1,
0x20, opcode::PUSH1,
0x00, opcode::RETURN,
];
let create_code = vec![
opcode::PUSH1,
init_code.len() as u8, opcode::PUSH1,
0x20, opcode::PUSH1,
0x00, opcode::CODECOPY,
opcode::PUSH1,
init_code.len() as u8, opcode::PUSH1,
0x00, opcode::PUSH1,
0x00, opcode::CREATE,
opcode::STOP,
];
let mut full_code = create_code;
full_code.extend_from_slice(&init_code);
let bytecode = Bytecode::new_raw(Bytes::from(full_code));
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let create_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Create { inputs, outcome } = e {
Some((inputs, outcome))
} else {
None
}
})
.collect();
assert!(
!create_events.is_empty(),
"Should have recorded CREATE events"
);
let (_create_inputs, create_outcome) = &create_events[0];
assert!(create_outcome.is_some(), "CREATE should have completed");
}
#[test]
fn test_log_operations() {
let code = vec![
opcode::PUSH1,
0x42,
opcode::PUSH1,
0x00,
opcode::MSTORE,
opcode::PUSH1,
0x20, opcode::PUSH1,
0x00, opcode::LOG0,
opcode::STOP,
];
let bytecode = Bytecode::new_raw(Bytes::from(code));
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let log_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Log(log) = e {
Some(log)
} else {
None
}
})
.collect();
assert_eq!(log_events.len(), 1, "Should have recorded one LOG event");
let log = &log_events[0];
assert_eq!(log.topics().len(), 0, "LOG0 should have 0 topics");
}
#[test]
fn test_selfdestruct() {
let beneficiary = address!("3000000000000000000000000000000000000000");
let mut code = vec![opcode::PUSH20];
code.extend_from_slice(beneficiary.as_ref());
code.push(opcode::SELFDESTRUCT);
let bytecode = Bytecode::new_raw(Bytes::from(code));
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let selfdestruct_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Selfdestruct {
address,
beneficiary,
value,
} = e
{
Some((address, beneficiary, value))
} else {
None
}
})
.collect();
assert_eq!(
selfdestruct_events.len(),
1,
"Should have recorded SELFDESTRUCT event"
);
let (_address, event_beneficiary, _value) = selfdestruct_events[0];
assert_eq!(*event_beneficiary, beneficiary);
}
#[test]
fn test_comprehensive_inspector_integration() {
let code = vec![
opcode::PUSH1,
0x10,
opcode::PUSH1,
0x20,
opcode::ADD,
opcode::DUP1,
opcode::PUSH1,
0x00,
opcode::MSTORE,
opcode::PUSH1,
0x01,
opcode::PUSH1,
0x00,
opcode::MLOAD,
opcode::GT,
opcode::PUSH1,
0x17, opcode::JUMPI,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::REVERT,
opcode::JUMPDEST, opcode::PUSH1,
0x20,
opcode::PUSH1,
0x00,
opcode::LOG0,
opcode::STOP,
];
let bytecode = Bytecode::new_raw(Bytes::from(code));
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let _ = evm.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.build()
.unwrap(),
);
let inspector = &evm.inspector;
let events = inspector.get_events();
let step_count = events
.iter()
.filter(|e| matches!(e, InspectorEvent::Step(_)))
.count();
let log_count = events
.iter()
.filter(|e| matches!(e, InspectorEvent::Log(_)))
.count();
assert!(step_count > 10, "Should have multiple step events");
assert_eq!(log_count, 1, "Should have one log event");
let step_events: Vec<_> = events
.iter()
.filter_map(|e| {
if let InspectorEvent::Step(record) = e {
Some(record)
} else {
None
}
})
.collect();
let add_event = step_events.iter().find(|e| e.opcode_name == "ADD").unwrap();
assert_eq!(add_event.before.stack_len, 2);
assert_eq!(add_event.after.as_ref().unwrap().stack_len, 1);
let mstore_event = step_events
.iter()
.find(|e| e.opcode_name == "MSTORE")
.unwrap();
assert!(mstore_event.after.as_ref().unwrap().memory_size > 0);
let jumpi_event = step_events
.iter()
.find(|e| e.opcode_name == "JUMPI")
.unwrap();
assert_eq!(
jumpi_event.after.as_ref().unwrap().pc,
0x17,
"Should have jumped to JUMPDEST"
);
}
#[test]
fn test_system_call_inspection_basic() {
let code = Bytes::from(vec![
opcode::PUSH1,
0x42,
opcode::PUSH1,
0x00,
opcode::SSTORE,
opcode::STOP,
]);
let bytecode = Bytecode::new_raw(code);
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm
.inspect_system_call(BENCH_TARGET, Bytes::default())
.unwrap();
assert!(result.result.is_success());
assert!(evm.inspector.get_step_count() > 0);
assert!(!result.state.is_empty());
}
#[test]
fn test_system_call_inspection_api_variants() {
let code = vec![
opcode::CALLER,
opcode::PUSH1,
0x00,
opcode::MSTORE,
opcode::PUSH1,
0x20,
opcode::PUSH1,
0x00,
opcode::RETURN,
];
let bytecode = Bytecode::new_raw(Bytes::from(code));
let ctx = Context::mainnet().with_db(BenchmarkDB::new_bytecode(bytecode));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm
.inspect_one_system_call(BENCH_TARGET, Bytes::default())
.unwrap();
assert!(result.is_success());
let custom_caller = address!("0x1234567890123456789012345678901234567890");
let result = evm
.inspect_one_system_call_with_caller(custom_caller, BENCH_TARGET, Bytes::default())
.unwrap();
assert!(result.is_success());
let result = evm
.inspect_one_system_call_with_inspector(
BENCH_TARGET,
Bytes::default(),
TestInspector::new(),
)
.unwrap();
assert!(result.is_success());
assert!(evm.inspector.get_step_count() > 0);
}
}