use alloy_primitives::{Address, Log, U256};
use edb_common::types::{CallResult, Trace, TraceEntry};
use revm::{
context::ContextTr,
interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter},
Inspector,
};
use std::{collections::HashMap, ops::Deref};
use tracing::{debug, error};
#[derive(Debug)]
pub struct TraceReplayResult {
pub visited_addresses: HashMap<Address, bool>,
pub execution_trace: Trace,
}
#[derive(Debug, Default)]
pub struct CallTracer {
pub trace: Trace,
pub visited_addresses: HashMap<Address, bool>,
call_stack: Vec<usize>,
}
impl CallTracer {
pub fn new() -> Self {
Self { trace: Trace::default(), visited_addresses: HashMap::new(), call_stack: Vec::new() }
}
pub fn visited_addresses(&self) -> &HashMap<Address, bool> {
&self.visited_addresses
}
pub fn execution_trace(&self) -> &Trace {
&self.trace
}
pub fn into_replay_result(self) -> TraceReplayResult {
TraceReplayResult { visited_addresses: self.visited_addresses, execution_trace: self.trace }
}
fn mark_address_visited(&mut self, address: Address, deployed: bool) {
self.visited_addresses
.entry(address)
.and_modify(|existing| *existing |= deployed)
.or_insert(deployed);
}
}
impl<CTX: ContextTr> Inspector<CTX> for CallTracer {
fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
let Some(entry) = self.trace.last_mut() else {
debug!("Trace is empty, cannot step");
return;
};
if entry.bytecode.is_some() {
return;
}
entry.bytecode = Some(interp.bytecode.bytes());
}
fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
let call_type = inputs.into();
let target = inputs.target_address;
let code_address = inputs.bytecode_address;
let caller = inputs.caller;
self.mark_address_visited(target, false);
self.mark_address_visited(code_address, false);
self.mark_address_visited(caller, false);
let parent_id = self.call_stack.last().copied();
let trace_id = self.trace.len();
let trace_entry = TraceEntry {
id: trace_id,
parent_id,
depth: self.call_stack.len(),
call_type,
caller,
target,
code_address,
input: inputs.input.bytes(context),
value: inputs.transfer_value().unwrap_or(U256::ZERO),
result: None, events: vec![], self_destruct: None, created_contract: false,
create_scheme: None,
bytecode: None, target_label: None, first_snapshot_id: None, };
self.trace.push(trace_entry);
self.call_stack.push(trace_id);
None }
fn call_end(&mut self, _context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
let Some(trace_index) = self.call_stack.pop() else {
error!("Call stack underflow - no matching call entry found");
return;
};
let Some(trace_entry) = self.trace.get_mut(trace_index) else {
error!("Call stack entry not found");
return;
};
trace_entry.result = Some(outcome.into());
let target = inputs.target_address;
let code_address = inputs.bytecode_address;
let caller = inputs.caller;
if trace_entry.target != target
|| trace_entry.code_address != code_address
|| trace_entry.caller != caller
{
error!("Call stack entry mismatch");
}
}
fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
let call_type = inputs.into();
let caller = inputs.caller;
self.mark_address_visited(caller, false);
let parent_id = self.call_stack.last().copied();
let trace_id = self.trace.len();
let trace_entry = TraceEntry {
id: trace_id,
parent_id,
depth: self.call_stack.len(),
call_type,
caller,
target: Address::ZERO, code_address: Address::ZERO, input: inputs.init_code.clone(),
value: inputs.value,
result: None, events: vec![], self_destruct: None, created_contract: false, create_scheme: Some(inputs.scheme),
bytecode: None, target_label: None, first_snapshot_id: None, };
self.trace.push(trace_entry);
self.call_stack.push(trace_id);
None }
fn create_end(
&mut self,
_context: &mut CTX,
inputs: &CreateInputs,
outcome: &mut CreateOutcome,
) {
let Some(trace_index) = self.call_stack.pop() else {
error!("Call stack underflow - no matching create entry found");
return;
};
let Some(trace_entry) = self.trace.get_mut(trace_index) else {
error!("Trace entry not found");
return;
};
let caller = inputs.caller;
if trace_entry.caller != caller {
error!("Create stack entry mismatch");
}
trace_entry.result = Some(outcome.into());
if matches!(trace_entry.result, Some(CallResult::Revert { .. })) {
debug!("Creation failed");
return;
}
let Some(created_address) = outcome.address else {
error!("Create outcome did not provide created address");
return;
};
trace_entry.target = created_address;
trace_entry.code_address = created_address;
trace_entry.created_contract = true;
let created_address_for_marking = trace_entry.target;
let _ = trace_entry;
self.mark_address_visited(created_address_for_marking, true);
}
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
self.mark_address_visited(contract, false);
self.mark_address_visited(target, false);
let Some(entry) = self.trace.last_mut() else {
error!("Trace is empty, cannot step");
return;
};
if entry.target != contract {
error!("Self-destruct entry mismatch");
return;
}
entry.self_destruct = Some((target, value));
}
fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) {
let Some(entry) = self.trace.last_mut() else {
error!("Trace is empty, cannot log");
return;
};
entry.events.push(log.deref().clone());
}
}