pub use crate::opcode_tracer::{LevmOpcodeTracer, OpcodeTracerConfig};
use crate::{
errors::{ContextResult, InternalError, TxResult, VMError},
vm::VM,
};
use bytes::Bytes;
use ethrex_common::{
Address, U256,
tracing::{CallLog, CallTraceFrame, CallType},
types::Log,
};
#[derive(Debug, Default)]
pub struct LevmCallTracer {
pub callframes: Vec<CallTraceFrame>,
pub only_top_call: bool,
pub with_log: bool,
pub active: bool,
}
impl LevmCallTracer {
pub fn new(only_top_call: bool, with_log: bool) -> Self {
LevmCallTracer {
callframes: vec![],
only_top_call,
with_log,
active: true,
}
}
pub fn disabled() -> Self {
LevmCallTracer {
active: false,
..Default::default()
}
}
pub fn enter(
&mut self,
call_type: CallType,
from: Address,
to: Address,
value: U256,
gas: u64,
input: &Bytes, ) {
if !self.active {
return;
}
if self.only_top_call && !self.callframes.is_empty() {
return;
}
let callframe = CallTraceFrame {
call_type,
from,
to,
value,
gas,
input: input.clone(),
..Default::default()
};
self.callframes.push(callframe);
}
fn exit(
&mut self,
gas_used: u64,
output: Bytes,
error: Option<String>,
revert_reason: Option<String>,
) -> Result<(), InternalError> {
let mut callframe = self.callframes.pop().ok_or(InternalError::CallFrame)?;
process_output(&mut callframe, gas_used, output, error, revert_reason);
if let Some(parent_callframe) = self.callframes.last_mut() {
parent_callframe.calls.push(callframe);
} else {
self.callframes.push(callframe);
};
Ok(())
}
pub fn exit_context(
&mut self,
ctx_result: &ContextResult,
is_top_call: bool,
) -> Result<(), InternalError> {
if !self.active {
return Ok(());
}
if self.only_top_call && !is_top_call {
return Ok(());
}
if is_top_call {
clear_reverted_logs(self.current_callframe_mut()?);
}
let (gas_used, output) = (ctx_result.gas_used, ctx_result.output.clone());
let (error, revert_reason) = match ctx_result.result {
TxResult::Revert(ref err) => {
let reason = String::from_utf8(ctx_result.output.to_vec()).ok();
(Some(err.to_string()), reason)
}
_ => (None, None),
};
self.exit(gas_used, output, error, revert_reason)
}
pub fn exit_early(
&mut self,
gas_used: u64,
error: Option<String>,
) -> Result<(), InternalError> {
if !self.active || self.only_top_call {
return Ok(());
}
self.exit(gas_used, Bytes::new(), error, None)
}
pub fn log(&mut self, log: &Log) -> Result<(), InternalError> {
if !self.active || !self.with_log {
return Ok(());
}
if self.only_top_call && self.callframes.len() > 1 {
return Ok(());
}
let callframe = self.current_callframe_mut()?;
let log = CallLog {
address: log.address,
topics: log.topics.clone(),
data: log.data.clone(),
position: match callframe.calls.len().try_into() {
Ok(pos) => pos,
Err(_) => return Err(InternalError::TypeConversion),
},
};
callframe.logs.push(log);
Ok(())
}
fn current_callframe_mut(&mut self) -> Result<&mut CallTraceFrame, InternalError> {
self.callframes.last_mut().ok_or(InternalError::CallFrame)
}
}
fn process_output(
callframe: &mut CallTraceFrame,
gas_used: u64,
output: Bytes,
error: Option<String>,
revert_reason: Option<String>,
) {
callframe.gas_used = gas_used;
callframe.output = output;
callframe.error = error;
callframe.revert_reason = revert_reason;
}
fn clear_reverted_logs(callframe: &mut CallTraceFrame) {
if callframe.error.is_some() {
callframe.logs.clear();
}
for subcall in &mut callframe.calls {
clear_reverted_logs(subcall);
}
}
impl<'a> VM<'a> {
pub fn get_trace_result(&mut self) -> Result<CallTraceFrame, VMError> {
self.tracer
.callframes
.pop()
.ok_or(InternalError::CallFrame.into())
}
}