pub use crate::opcode_tracer::{LevmOpcodeTracer, OpcodeTracerConfig};
use crate::{
errors::{ContextResult, InternalError, TxResult, VMError},
memory::CallData,
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,
pub next_log_index: u64,
}
impl LevmCallTracer {
pub fn new(only_top_call: bool, with_log: bool, log_index_base: u64) -> Self {
LevmCallTracer {
callframes: vec![],
only_top_call,
with_log,
active: true,
next_log_index: log_index_base,
}
}
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: &CallData,
) -> Result<(), VMError> {
if !self.active {
return Ok(());
}
let value = if matches!(call_type, CallType::STATICCALL) {
None
} else {
Some(value)
};
let callframe = CallTraceFrame {
call_type,
from,
to: Some(to),
value,
gas,
input: input.to_bytes()?,
..Default::default()
};
self.callframes.push(callframe);
Ok(())
}
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 is_top_call {
let top_failed = matches!(ctx_result.result, TxResult::Revert(_));
clear_reverted_logs(self.current_callframe_mut()?, top_failed);
let mut next_index = self.next_log_index;
assign_log_indices(self.current_callframe_mut()?, &mut next_index);
self.next_log_index = next_index;
if self.only_top_call {
let callframe = self.current_callframe_mut()?;
callframe.calls.clear();
for log in &mut callframe.logs {
log.position = 0;
}
}
}
let gas_used = if is_top_call {
ctx_result.gas_spent
} else {
ctx_result.gas_used
};
let output = ctx_result.output.clone();
let (output, error, revert_reason) = match ctx_result.result {
TxResult::Revert(ref err) => {
let error = Some(geth_error_string(err));
if err.is_revert_opcode() && !output.is_empty() {
(output.clone(), error, decode_revert_reason(&output))
} else {
(Bytes::new(), error, None)
}
}
_ => (output, 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 {
return Ok(());
}
let error = error.map(|token| geth_error_from_token(&token));
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(());
}
let callframe = self.current_callframe_mut()?;
let log = CallLog {
address: log.address,
topics: log.topics.clone(),
data: log.data.clone(),
index: 0,
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;
if error.is_some() && matches!(callframe.call_type, CallType::CREATE | CallType::CREATE2) {
callframe.to = None;
}
callframe.error = error;
callframe.revert_reason = revert_reason;
}
fn geth_error_string(err: &VMError) -> String {
use crate::errors::ExceptionalHalt::*;
let mapped = match err {
VMError::RevertOpcode => "execution reverted",
VMError::ExceptionalHalt(halt) => match halt {
OutOfGas => "out of gas",
InvalidJump => "invalid jump destination",
OpcodeNotAllowedInStaticContext => "write protection",
AddressAlreadyOccupied => "contract address collision",
ContractOutputTooBig => "max code size exceeded",
InvalidContractPrefix => "invalid code: must not begin with 0xef",
StackUnderflow => "stack underflow",
StackOverflow => "stack overflow",
InvalidOpcode => "invalid opcode",
VeryLargeNumber | OutOfBounds | Precompile(_) => return err.to_string(),
},
_ => return err.to_string(),
};
mapped.to_string()
}
fn geth_error_from_token(token: &str) -> String {
match token {
"OutOfFund" => "insufficient balance for transfer",
"MaxDepth" => "max call depth exceeded",
"MaxNonce" => "nonce uint64 overflow",
"CreateAccExists" => "contract address collision",
other => return other.to_string(),
}
.to_string()
}
fn decode_revert_reason(output: &[u8]) -> Option<String> {
const SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0];
if output.get(..4)? != SELECTOR {
return None;
}
let word_as_usize = |start: usize| -> Option<usize> {
let word = output.get(start..start.checked_add(32)?)?;
usize::try_from(U256::from_big_endian(word)).ok()
};
let len_start = 4usize.checked_add(word_as_usize(4)?)?;
let len = word_as_usize(len_start)?;
let data_start = len_start.checked_add(32)?;
let data = output.get(data_start..data_start.checked_add(len)?)?;
String::from_utf8(data.to_vec()).ok()
}
fn assign_log_indices(callframe: &mut CallTraceFrame, next: &mut u64) {
let mut logs = callframe.logs.iter_mut().peekable();
for (call_idx, subcall) in callframe.calls.iter_mut().enumerate() {
let call_idx = u64::try_from(call_idx).unwrap_or(u64::MAX);
while let Some(log) = logs.next_if(|l| l.position == call_idx) {
log.index = *next;
*next = next.saturating_add(1);
}
assign_log_indices(subcall, next);
}
for log in logs {
log.index = *next;
*next = next.saturating_add(1);
}
}
fn clear_reverted_logs(callframe: &mut CallTraceFrame, parent_failed: bool) {
let failed = parent_failed || callframe.error.is_some();
if failed {
callframe.logs.clear();
}
for subcall in &mut callframe.calls {
clear_reverted_logs(subcall, failed);
}
}
impl<'a> VM<'a> {
pub fn get_trace_result(&mut self) -> Result<CallTraceFrame, VMError> {
self.tracer
.callframes
.pop()
.ok_or(InternalError::CallFrame.into())
}
}