use alloy::{consensus::constants::SELECTOR_LEN, hex};
use revm::{
context::{ContextTr, LocalContextTr},
interpreter::{
CallInput, CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter,
InterpreterTypes,
},
Inspector,
};
use tracing::{
debug_span, error_span, info_span, span::EnteredSpan, trace_span, warn_span, Level, Span,
};
macro_rules! runtime_level_span {
($level:expr, $($args:tt)*) => {{
match $level {
Level::TRACE => trace_span!($($args)*),
Level::DEBUG => debug_span!($($args)*),
Level::INFO => info_span!($($args)*),
Level::WARN => warn_span!($($args)*),
Level::ERROR => error_span!($($args)*),
}
}};
}
pub struct SpanningInspector {
active: Vec<EnteredSpan>,
level: Level,
}
impl core::fmt::Debug for SpanningInspector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpanningInspector")
.field("active", &self.active.len())
.field("level", &self.level)
.finish()
}
}
impl SpanningInspector {
pub const fn new(level: Level) -> Self {
Self { active: Vec::new(), level }
}
pub const fn at_trace() -> Self {
Self::new(Level::TRACE)
}
pub const fn at_debug() -> Self {
Self::new(Level::DEBUG)
}
pub const fn at_info() -> Self {
Self::new(Level::INFO)
}
fn root_span<Ctx, Int>(&mut self, _interp: &mut Interpreter<Int>, _context: &mut Ctx) -> Span
where
Int: InterpreterTypes,
{
runtime_level_span!(
self.level,
parent: None, "evm_execution",
)
}
fn init<Ctx, Int>(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx)
where
Int: InterpreterTypes,
{
self.active.clear();
let span = self.root_span(interp, context).entered();
self.active.push(span);
}
fn exit_span(&mut self) {
self.active.pop();
if self.active.len() == 1 {
self.active.pop();
}
}
fn span_call<Ctx>(&self, context: &mut Ctx, inputs: &CallInputs) -> Span
where
Ctx: ContextTr,
{
let selector = resolve_selector(inputs, context).map(hex::encode);
runtime_level_span!(
self.level,
"call",
input_len = inputs.input.len(),
selector,
gas_limit = inputs.gas_limit,
bytecode_address = %inputs.bytecode_address,
target_addrses = %inputs.target_address,
caller = %inputs.caller,
value = %inputs.value.get(),
scheme = ?inputs.scheme,
)
}
fn enter_call<Ctx>(&mut self, context: &mut Ctx, inputs: &CallInputs)
where
Ctx: ContextTr,
{
self.active.push(self.span_call(context, inputs).entered())
}
fn span_create<Ctx>(&self, _context: &Ctx, inputs: &CreateInputs) -> Span {
runtime_level_span!(
self.level,
"create",
caller = %inputs.caller(),
value = %inputs.value(),
gas_limit = inputs.gas_limit(),
scheme = ?inputs.scheme(),
)
}
fn enter_create<Ctx>(&mut self, context: &Ctx, inputs: &CreateInputs) {
self.active.push(self.span_create(context, inputs).entered())
}
}
impl<Ctx, Int> Inspector<Ctx, Int> for SpanningInspector
where
Int: InterpreterTypes,
Ctx: ContextTr,
{
fn initialize_interp(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.init(interp, context);
}
fn call(&mut self, context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
self.enter_call(context, inputs);
None
}
fn call_end(&mut self, _context: &mut Ctx, _inputs: &CallInputs, _outcome: &mut CallOutcome) {
self.exit_span();
}
fn create(&mut self, context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
self.enter_create(context, inputs);
None
}
fn create_end(
&mut self,
_context: &mut Ctx,
_inputs: &CreateInputs,
_outcome: &mut CreateOutcome,
) {
self.exit_span();
}
}
fn resolve_selector(inputs: &CallInputs, ctx: &mut impl ContextTr) -> Option<[u8; SELECTOR_LEN]> {
match &inputs.input {
CallInput::SharedBuffer(range) => {
let raw = ctx.local().shared_memory_buffer_slice(range.clone());
raw?.get(..SELECTOR_LEN).map(TryInto::try_into).and_then(Result::ok)
}
CallInput::Bytes(bytes) => {
bytes.as_ref().get(..SELECTOR_LEN).map(TryInto::try_into).and_then(Result::ok)
}
}
}