#[cfg(test)]
mod tests {
use crate::{
InspectCommitEvm, InspectEvm, InspectSystemCallEvm, InspectorEvent, TestInspector,
};
use context::{CfgEnv, Context, TxEnv};
use database::{BenchmarkDB, BENCH_CALLER, BENCH_TARGET};
use handler::{ExecuteEvm, MainBuilder, MainContext};
use primitives::{
address,
eip7708::{ETH_TRANSFER_LOG_ADDRESS, ETH_TRANSFER_LOG_TOPIC},
hardfork::SpecId,
Address, Bytes, TxKind, B256, 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_eip7708_tx_value_transfer_log_is_inspected() {
let recipient = address!("4000000000000000000000000000000000000000");
let value = U256::from(1_000_000_000_000_000u128);
let ctx = Context::mainnet()
.with_cfg(CfgEnv::new_with_spec(SpecId::AMSTERDAM))
.with_db(BenchmarkDB::new_bytecode(Bytecode::new()));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm
.inspect_one_tx(
TxEnv::builder_for_bench()
.to(recipient)
.value(value)
.gas_limit(300_000)
.gas_price(0)
.build_fill(),
)
.unwrap();
assert!(result.is_success());
let events = evm.inspector.get_events();
let transfer_log = events.iter().find_map(|event| {
let InspectorEvent::Log(log) = event else {
return None;
};
(log.address == ETH_TRANSFER_LOG_ADDRESS
&& log.data.topics().len() == 3
&& log.data.topics()[0] == ETH_TRANSFER_LOG_TOPIC
&& log.data.topics()[1] == B256::left_padding_from(BENCH_CALLER.as_slice())
&& log.data.topics()[2] == B256::left_padding_from(recipient.as_slice()))
.then_some(log)
});
assert!(
transfer_log.is_some(),
"expected inspector to receive EIP-7708 tx value transfer log"
);
}
#[test]
fn test_eip7708_selfdestruct_transfer_log_is_inspected() {
let code = Bytes::from(vec![opcode::CALLER, opcode::SELFDESTRUCT, opcode::STOP]);
let ctx = Context::mainnet()
.with_cfg(CfgEnv::new_with_spec(SpecId::AMSTERDAM))
.with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy(code)));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm
.inspect_one_tx(
TxEnv::builder_for_bench()
.gas_limit(100_000)
.gas_price(0)
.build_fill(),
)
.unwrap();
assert!(result.is_success());
let events = evm.inspector.get_events();
let transfer_log = events.iter().find_map(|event| {
let InspectorEvent::Log(log) = event else {
return None;
};
(log.address == ETH_TRANSFER_LOG_ADDRESS
&& log.data.topics().len() == 3
&& log.data.topics()[0] == ETH_TRANSFER_LOG_TOPIC
&& log.data.topics()[1] == B256::left_padding_from(BENCH_TARGET.as_slice())
&& log.data.topics()[2] == B256::left_padding_from(BENCH_CALLER.as_slice()))
.then_some(log)
});
assert!(
transfer_log.is_some(),
"expected inspector to receive EIP-7708 selfdestruct transfer log"
);
}
#[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 cancun_selfdestruct_to_self_does_not_reuse_prior_journal_entry() {
let code = Bytes::from(vec![opcode::ADDRESS, opcode::SELFDESTRUCT]);
let ctx = Context::mainnet()
.with_cfg(CfgEnv::new_with_spec(SpecId::CANCUN))
.with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy(code)));
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm
.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.value(U256::from(459))
.gas_limit(100_000)
.gas_price(0)
.build()
.unwrap(),
)
.unwrap();
assert!(result.is_success());
assert!(
!evm.inspector
.get_events()
.iter()
.any(|event| matches!(event, InspectorEvent::Selfdestruct { .. })),
"Cancun selfdestruct-to-self must not reuse the transaction value-transfer journal entry"
);
}
#[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);
}
#[test]
fn test_system_call_gas_consistency_with_reservoir() {
use database::{CacheDB, EmptyDB};
use handler::SystemCallEvm;
use primitives::hardfork::SpecId;
let child_addr = address!("0x000000000000000000000000000000000000c0de");
let child_code = Bytes::from(vec![
opcode::PUSH1,
0x42,
opcode::PUSH1,
0x00,
opcode::SSTORE,
opcode::PUSH0,
opcode::PUSH0,
opcode::REVERT,
]);
let system_code = Bytes::from(vec![
opcode::PUSH1,
0x00, 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,
0xc0,
0xde, opcode::PUSH4,
0x00,
0xFF,
0xFF,
0xFF, opcode::CALL,
opcode::POP,
opcode::STOP,
]);
let make_db = || {
let mut db = CacheDB::<EmptyDB>::default();
db.insert_account_info(
BENCH_TARGET,
AccountInfo {
balance: U256::ZERO,
nonce: 0,
code_hash: primitives::keccak256(&system_code),
code: Some(Bytecode::new_raw(system_code.clone())),
..Default::default()
},
);
db.insert_account_info(
child_addr,
AccountInfo {
balance: U256::ZERO,
nonce: 0,
code_hash: primitives::keccak256(&child_code),
code: Some(Bytecode::new_raw(child_code.clone())),
..Default::default()
},
);
db
};
let make_ctx = || {
Context::mainnet()
.modify_cfg_chained(|c| c.set_spec_and_mainnet_gas_params(SpecId::AMSTERDAM))
.with_db(make_db())
};
let mut evm_plain = make_ctx().build_mainnet();
let result_plain = evm_plain
.system_call_one(BENCH_TARGET, Bytes::default())
.expect("non-inspect system call must succeed");
let mut evm_inspect = make_ctx().build_mainnet_with_inspector(TestInspector::new());
let result_inspect = evm_inspect
.inspect_one_system_call(BENCH_TARGET, Bytes::default())
.expect("inspect system call must succeed");
assert!(result_plain.is_success(), "non-inspect must succeed");
assert!(result_inspect.is_success(), "inspect must succeed");
let plain_total = result_plain.gas().total_gas_spent();
let inspect_total = result_inspect.gas().total_gas_spent();
assert_eq!(
plain_total, inspect_total,
"system_call total_gas_spent must match between inspect and non-inspect paths",
);
assert_eq!(
result_plain.gas().state_gas_spent_final(),
result_inspect.gas().state_gas_spent_final(),
"system_call state_gas_spent must match between inspect and non-inspect paths",
);
}
#[test]
fn test_inspect_tx_finalizes_journal_on_error() {
use database::{CacheDB, EmptyDB};
let mut db = CacheDB::<EmptyDB>::default();
db.insert_account_info(
BENCH_CALLER,
AccountInfo {
balance: U256::from(1_000_000_000_000_000_000u64),
nonce: 1,
..Default::default()
},
);
let ctx = Context::mainnet().with_db(db);
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm.inspect_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.nonce(0)
.build()
.unwrap(),
);
assert!(
result.is_err(),
"tx with stale nonce must error so the error path is exercised"
);
let leftover = evm.finalize();
assert!(
leftover.is_empty(),
"journal must be cleared after a failed inspect_tx, but found {} leftover accounts",
leftover.len(),
);
}
#[test]
fn test_inspect_tx_commit_finalizes_journal_on_error() {
use database::{CacheDB, EmptyDB};
let mut db = CacheDB::<EmptyDB>::default();
db.insert_account_info(
BENCH_CALLER,
AccountInfo {
balance: U256::from(1_000_000_000_000_000_000u64),
nonce: 1,
..Default::default()
},
);
let ctx = Context::mainnet().with_db(db);
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm.inspect_tx_commit(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(100_000)
.nonce(0)
.build()
.unwrap(),
);
assert!(
result.is_err(),
"tx with stale nonce must error so the error path is exercised"
);
let leftover = evm.finalize();
assert!(
leftover.is_empty(),
"journal must be cleared after a failed inspect_tx_commit, but found {} leftover accounts",
leftover.len(),
);
}
fn transfer_logs(events: &[InspectorEvent]) -> Vec<(Address, Address, U256)> {
events
.iter()
.filter_map(|event| {
let InspectorEvent::Log(log) = event else {
return None;
};
if log.address != ETH_TRANSFER_LOG_ADDRESS
|| log.data.topics().first() != Some(Ð_TRANSFER_LOG_TOPIC)
{
return None;
}
Some((
Address::from_word(log.data.topics()[1]),
Address::from_word(log.data.topics()[2]),
U256::from_be_slice(log.data.data.as_ref()),
))
})
.collect()
}
const CALLEE: Address = address!("5000000000000000000000000000000000000000");
fn run_transfer_log_probe(
code: Vec<u8>,
callee_code: Option<Bytecode>,
) -> Vec<(Address, Address, U256)> {
let mut db = database::CacheDB::<database::EmptyDB>::default();
db.insert_account_info(
BENCH_CALLER,
AccountInfo {
balance: U256::from(1_000_000_000u64),
..Default::default()
},
);
db.insert_account_info(
BENCH_TARGET,
AccountInfo {
balance: U256::from(1_000u64),
code_hash: Bytecode::new_legacy(Bytes::from(code.clone())).hash_slow(),
code: Some(Bytecode::new_legacy(Bytes::from(code))),
..Default::default()
},
);
if let Some(callee_code) = callee_code {
db.insert_account_info(
CALLEE,
AccountInfo {
code_hash: callee_code.hash_slow(),
code: Some(callee_code),
..Default::default()
},
);
}
let ctx = Context::mainnet()
.with_cfg(CfgEnv::new_with_spec(SpecId::AMSTERDAM))
.with_db(db);
let mut evm = ctx.build_mainnet_with_inspector(TestInspector::new());
let result = evm
.inspect_one_tx(
TxEnv::builder()
.caller(BENCH_CALLER)
.kind(TxKind::Call(BENCH_TARGET))
.gas_limit(1_000_000)
.gas_price(0)
.build()
.unwrap(),
)
.unwrap();
assert!(result.is_success(), "tx failed: {result:?}");
transfer_logs(&evm.inspector.get_events())
}
#[test]
fn test_eip7708_nested_call_transfer_log_is_inspected() {
let mut code = vec![
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x07,
opcode::PUSH20,
];
code.extend_from_slice(CALLEE.as_slice());
code.extend_from_slice(&[opcode::PUSH2, 0xFF, 0xFF, opcode::CALL, opcode::STOP]);
let logs = run_transfer_log_probe(
code,
Some(Bytecode::new_legacy(Bytes::from(vec![opcode::STOP]))),
);
assert_eq!(
logs,
vec![(BENCH_TARGET, CALLEE, U256::from(7))],
"nested CALL transfer log missing"
);
}
#[test]
fn test_eip7708_call_to_eoa_transfer_log_is_inspected() {
let mut code = vec![
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x07,
opcode::PUSH20,
];
code.extend_from_slice(CALLEE.as_slice());
code.extend_from_slice(&[opcode::PUSH2, 0xFF, 0xFF, opcode::CALL, opcode::STOP]);
let logs = run_transfer_log_probe(code, None);
assert_eq!(
logs,
vec![(BENCH_TARGET, CALLEE, U256::from(7))],
"EOA CALL transfer log missing"
);
}
#[test]
fn test_eip7708_call_to_precompile_transfer_log_is_inspected() {
let identity = address!("0000000000000000000000000000000000000004");
let mut code = vec![
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x07,
opcode::PUSH20,
];
code.extend_from_slice(identity.as_slice());
code.extend_from_slice(&[opcode::PUSH2, 0xFF, 0xFF, opcode::CALL, opcode::STOP]);
let logs = run_transfer_log_probe(code, None);
assert_eq!(
logs,
vec![(BENCH_TARGET, identity, U256::from(7))],
"precompile CALL transfer log missing"
);
}
#[test]
fn test_eip7708_create_transfer_log_is_inspected() {
let code = vec![
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x00,
opcode::MSTORE8,
opcode::PUSH1,
0x01,
opcode::PUSH1,
0x00,
opcode::PUSH1,
0x07,
opcode::CREATE,
opcode::STOP,
];
let logs = run_transfer_log_probe(code, None);
assert_eq!(logs.len(), 1, "CREATE transfer log missing: {logs:?}");
assert_eq!(logs[0].0, BENCH_TARGET);
assert_eq!(logs[0].2, U256::from(7));
}
}