use alloy_primitives::{Address, Bytes, Log, U256};
use revm::Inspector;
use revm::interpreter::{
CallInput, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, CreateScheme,
Interpreter, InterpreterTypes,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CallKind {
Call,
StaticCall,
DelegateCall,
CallCode,
Create,
Create2,
}
impl CallKind {
fn from_call_scheme(scheme: CallScheme) -> Self {
match scheme {
CallScheme::Call => CallKind::Call,
CallScheme::CallCode => CallKind::CallCode,
CallScheme::DelegateCall => CallKind::DelegateCall,
CallScheme::StaticCall => CallKind::StaticCall,
}
}
fn from_create_scheme(scheme: CreateScheme) -> Self {
match scheme {
CreateScheme::Create => CallKind::Create,
CreateScheme::Create2 { .. } => CallKind::Create2,
CreateScheme::Custom { .. } => CallKind::Create,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CallStatus {
Success,
Revert,
Halt,
}
#[derive(Clone, Debug)]
pub struct CallTrace {
pub kind: CallKind,
pub from: Address,
pub to: Address,
pub value: U256,
pub input: Bytes,
pub gas_used: u64,
pub output: Bytes,
pub status: CallStatus,
pub depth: usize,
pub subcalls: Vec<CallTrace>,
}
#[derive(Clone, Debug)]
struct PendingFrame {
kind: CallKind,
from: Address,
to: Address,
value: U256,
input: Bytes,
depth: usize,
subcalls: Vec<CallTrace>,
}
#[derive(Clone, Debug, Default)]
pub struct CallTracer {
stack: Vec<PendingFrame>,
root: Option<CallTrace>,
}
impl CallTracer {
pub fn new() -> Self {
Self::default()
}
pub fn root(&self) -> Option<&CallTrace> {
self.root.as_ref()
}
pub fn into_trace(self) -> Option<CallTrace> {
self.root
}
fn push_frame(
&mut self,
kind: CallKind,
from: Address,
to: Address,
value: U256,
input: Bytes,
) {
let depth = self.stack.len();
self.stack.push(PendingFrame {
kind,
from,
to,
value,
input,
depth,
subcalls: Vec::new(),
});
}
fn pop_frame(
&mut self,
gas_used: u64,
output: Bytes,
status: CallStatus,
to_override: Option<Address>,
) {
let Some(pending) = self.stack.pop() else {
return;
};
let trace = CallTrace {
kind: pending.kind,
from: pending.from,
to: to_override.unwrap_or(pending.to),
value: pending.value,
input: pending.input,
gas_used,
output,
status,
depth: pending.depth,
subcalls: pending.subcalls,
};
if let Some(parent) = self.stack.last_mut() {
parent.subcalls.push(trace);
} else {
self.root = Some(trace);
}
}
}
fn resolve_call_input(input: &CallInput) -> Bytes {
match input {
CallInput::Bytes(bytes) => bytes.clone(),
CallInput::SharedBuffer(_) => Bytes::new(),
}
}
fn status_from_result(result: revm::interpreter::InstructionResult) -> CallStatus {
if result.is_ok() {
CallStatus::Success
} else if result.is_revert() {
CallStatus::Revert
} else {
CallStatus::Halt
}
}
impl<CTX, INTR> Inspector<CTX, INTR> for CallTracer
where
INTR: InterpreterTypes,
{
fn call(&mut self, _context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
self.push_frame(
CallKind::from_call_scheme(inputs.scheme),
inputs.caller,
inputs.target_address,
inputs.call_value(),
resolve_call_input(&inputs.input),
);
None
}
fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, outcome: &mut CallOutcome) {
let status = status_from_result(*outcome.instruction_result());
let gas_used = outcome.gas().spent();
let output = outcome.output().clone();
self.pop_frame(gas_used, output, status, None);
}
fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
self.push_frame(
CallKind::from_create_scheme(inputs.scheme()),
inputs.caller(),
Address::ZERO,
inputs.value(),
inputs.init_code().clone(),
);
None
}
fn create_end(
&mut self,
_context: &mut CTX,
_inputs: &CreateInputs,
outcome: &mut CreateOutcome,
) {
let status = status_from_result(*outcome.instruction_result());
let gas_used = outcome.gas().spent();
let output = outcome.output().clone();
self.pop_frame(gas_used, output, status, outcome.address);
}
}
#[derive(Clone, Debug, Default)]
pub struct InspectorStack<A, B>(pub A, pub B);
impl<A, B, CTX, INTR> Inspector<CTX, INTR> for InspectorStack<A, B>
where
INTR: InterpreterTypes,
A: Inspector<CTX, INTR>,
B: Inspector<CTX, INTR>,
{
fn initialize_interp(&mut self, interp: &mut Interpreter<INTR>, context: &mut CTX) {
self.0.initialize_interp(interp, context);
self.1.initialize_interp(interp, context);
}
fn step(&mut self, interp: &mut Interpreter<INTR>, context: &mut CTX) {
self.0.step(interp, context);
self.1.step(interp, context);
}
fn step_end(&mut self, interp: &mut Interpreter<INTR>, context: &mut CTX) {
self.0.step_end(interp, context);
self.1.step_end(interp, context);
}
fn log(&mut self, context: &mut CTX, log: Log) {
self.0.log(context, log.clone());
self.1.log(context, log);
}
fn log_full(&mut self, interp: &mut Interpreter<INTR>, context: &mut CTX, log: Log) {
self.0.log_full(interp, context, log.clone());
self.1.log_full(interp, context, log);
}
fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
self.0
.call(context, inputs)
.or_else(|| self.1.call(context, inputs))
}
fn call_end(&mut self, context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
self.0.call_end(context, inputs, outcome);
self.1.call_end(context, inputs, outcome);
}
fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
self.0
.create(context, inputs)
.or_else(|| self.1.create(context, inputs))
}
fn create_end(
&mut self,
context: &mut CTX,
inputs: &CreateInputs,
outcome: &mut CreateOutcome,
) {
self.0.create_end(context, inputs, outcome);
self.1.create_end(context, inputs, outcome);
}
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
self.0.selfdestruct(contract, target, value);
self.1.selfdestruct(contract, target, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_tracer_has_no_root() {
let tracer = CallTracer::new();
assert!(tracer.root().is_none());
assert!(tracer.into_trace().is_none());
}
#[test]
fn call_kind_maps_call_schemes() {
assert_eq!(CallKind::from_call_scheme(CallScheme::Call), CallKind::Call);
assert_eq!(
CallKind::from_call_scheme(CallScheme::StaticCall),
CallKind::StaticCall
);
assert_eq!(
CallKind::from_call_scheme(CallScheme::DelegateCall),
CallKind::DelegateCall
);
assert_eq!(
CallKind::from_call_scheme(CallScheme::CallCode),
CallKind::CallCode
);
}
#[test]
fn call_kind_maps_create_schemes() {
assert_eq!(
CallKind::from_create_scheme(CreateScheme::Create),
CallKind::Create
);
assert_eq!(
CallKind::from_create_scheme(CreateScheme::Create2 { salt: U256::ZERO }),
CallKind::Create2
);
}
#[test]
fn resolve_input_reads_owned_bytes_and_empties_shared_buffer() {
let owned = CallInput::Bytes(Bytes::from(vec![1, 2, 3]));
assert_eq!(resolve_call_input(&owned), Bytes::from(vec![1, 2, 3]));
let shared = CallInput::SharedBuffer(0..8);
assert!(resolve_call_input(&shared).is_empty());
}
#[test]
fn status_mapping_classifies_results() {
use revm::interpreter::InstructionResult;
assert_eq!(
status_from_result(InstructionResult::Stop),
CallStatus::Success
);
assert_eq!(
status_from_result(InstructionResult::Return),
CallStatus::Success
);
assert_eq!(
status_from_result(InstructionResult::Revert),
CallStatus::Revert
);
assert_eq!(
status_from_result(InstructionResult::OutOfGas),
CallStatus::Halt
);
}
#[test]
fn frames_nest_into_a_tree() {
let mut tracer = CallTracer::new();
let root_addr = Address::repeat_byte(0x11);
let child_addr = Address::repeat_byte(0x22);
tracer.push_frame(
CallKind::Call,
Address::ZERO,
root_addr,
U256::ZERO,
Bytes::from(vec![0xaa]),
);
tracer.push_frame(
CallKind::StaticCall,
root_addr,
child_addr,
U256::ZERO,
Bytes::new(),
);
tracer.pop_frame(10, Bytes::new(), CallStatus::Success, None);
tracer.pop_frame(100, Bytes::from(vec![0xbb]), CallStatus::Success, None);
let root = tracer.into_trace().expect("root frame");
assert_eq!(root.to, root_addr);
assert_eq!(root.depth, 0);
assert_eq!(root.input, Bytes::from(vec![0xaa]));
assert_eq!(root.subcalls.len(), 1);
assert_eq!(root.subcalls[0].to, child_addr);
assert_eq!(root.subcalls[0].depth, 1);
assert_eq!(root.subcalls[0].kind, CallKind::StaticCall);
}
}