use crate::{inspectors::GasInspector, Inspector};
use context::{Cfg, ContextTr, JournalTr, Transaction};
use interpreter::{
interpreter_types::{Jumps, LoopControl, MemoryTr, StackTr},
CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, InterpreterResult,
InterpreterTypes, Stack,
};
use primitives::{hex, HashMap, B256, U256};
use serde::Serialize;
use state::bytecode::opcode::OpCode;
use std::io::Write;
pub struct TracerEip3155 {
output: Box<dyn Write>,
gas_inspector: GasInspector,
print_summary: bool,
stack: Vec<U256>,
pc: u64,
opcode: u8,
gas: u64,
refunded: i64,
mem_size: usize,
include_memory: bool,
memory: Option<String>,
}
impl std::fmt::Debug for TracerEip3155 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TracerEip3155")
.field("gas_inspector", &self.gas_inspector)
.field("print_summary", &self.print_summary)
.field("stack", &self.stack)
.field("pc", &self.pc)
.field("opcode", &self.opcode)
.field("gas", &self.gas)
.field("refunded", &self.refunded)
.field("mem_size", &self.mem_size)
.field("include_memory", &self.include_memory)
.field("memory", &self.memory)
.finish()
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Output<'a> {
pc: u64,
depth: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
op_name: Option<&'static str>,
op: u8,
#[serde(serialize_with = "serde_hex_u64")]
gas: u64,
#[serde(serialize_with = "serde_hex_u64")]
gas_cost: u64,
stack: &'a [U256],
return_data: &'static str,
#[serde(serialize_with = "serde_hex_u64")]
refund: u64,
#[serde(serialize_with = "serde_hex_u64")]
mem_size: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
memory: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
storage: Option<HashMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
return_stack: Option<Vec<String>>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Summary {
state_root: String,
output: String,
#[serde(serialize_with = "serde_hex_u64")]
gas_used: u64,
pass: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
time: Option<u128>,
#[serde(default, skip_serializing_if = "Option::is_none")]
fork: Option<String>,
}
impl TracerEip3155 {
pub fn buffered(output: impl Write + 'static) -> Self {
Self::new(Box::new(std::io::BufWriter::new(output)))
}
pub fn new_stdout() -> Self {
Self::buffered(std::io::stdout())
}
pub fn new(output: Box<dyn Write>) -> Self {
Self {
output,
gas_inspector: GasInspector::new(),
print_summary: true,
include_memory: false,
stack: Default::default(),
memory: Default::default(),
pc: 0,
opcode: 0,
gas: 0,
refunded: 0,
mem_size: 0,
}
}
pub fn set_writer(&mut self, writer: Box<dyn Write>) {
self.output = writer;
}
pub fn without_summary(mut self) -> Self {
self.print_summary = false;
self
}
pub fn with_memory(mut self) -> Self {
self.include_memory = true;
self
}
pub fn clear(&mut self) {
let Self {
gas_inspector,
stack,
pc,
opcode,
gas,
refunded,
mem_size,
..
} = self;
*gas_inspector = GasInspector::new();
stack.clear();
*pc = 0;
*opcode = 0;
*gas = 0;
*refunded = 0;
*mem_size = 0;
}
fn print_summary(&mut self, result: &InterpreterResult, context: &mut impl ContextTr) {
if !self.print_summary {
return;
}
let spec = context.cfg().spec().into();
let gas_limit = context.tx().gas_limit();
let value = Summary {
state_root: B256::ZERO.to_string(),
output: result.output.to_string(),
gas_used: gas_limit - self.gas_inspector.gas_remaining(),
pass: result.is_ok(),
time: None,
fork: Some(spec.to_string()),
};
let _ = self.write_value(&value);
}
fn write_value(&mut self, value: &impl serde::Serialize) -> std::io::Result<()> {
write_value(&mut *self.output, value)
}
}
pub trait CloneStack {
fn clone_into(&self, stack: &mut Vec<U256>);
}
impl CloneStack for Stack {
fn clone_into(&self, stack: &mut Vec<U256>) {
stack.extend_from_slice(self.data());
}
}
impl<CTX, INTR> Inspector<CTX, INTR> for TracerEip3155
where
CTX: ContextTr,
INTR: InterpreterTypes<Stack: StackTr + CloneStack>,
{
fn initialize_interp(&mut self, interp: &mut Interpreter<INTR>, _: &mut CTX) {
self.gas_inspector.initialize_interp(&interp.gas);
}
fn step(&mut self, interp: &mut Interpreter<INTR>, _: &mut CTX) {
self.gas_inspector.step(&interp.gas);
self.stack.clear();
interp.stack.clone_into(&mut self.stack);
self.memory = if self.include_memory {
Some(hex::encode_prefixed(
interp.memory.slice(0..interp.memory.size()).as_ref(),
))
} else {
None
};
self.pc = interp.bytecode.pc() as u64;
self.opcode = interp.bytecode.opcode();
self.mem_size = interp.memory.size();
self.gas = interp.gas.remaining();
self.refunded = interp.gas.refunded();
}
fn step_end(&mut self, interp: &mut Interpreter<INTR>, context: &mut CTX) {
self.gas_inspector.step_end(&interp.gas);
let value = Output {
pc: self.pc,
op: self.opcode,
gas: self.gas,
gas_cost: self.gas_inspector.last_gas_cost(),
stack: &self.stack,
depth: context.journal_mut().depth() as u64,
return_data: "0x",
refund: self.refunded as u64,
mem_size: self.mem_size as u64,
op_name: OpCode::new(self.opcode).map(|i| i.as_str()),
error: interp
.bytecode
.action()
.as_ref()
.and_then(|a| a.instruction_result())
.map(|ir| format!("{ir:?}")),
memory: self.memory.take(),
storage: None,
return_stack: None,
};
let _ = write_value(&mut self.output, &value);
}
fn call_end(&mut self, context: &mut CTX, _: &CallInputs, outcome: &mut CallOutcome) {
self.gas_inspector.call_end(outcome);
if context.journal_mut().depth() == 0 {
self.print_summary(&outcome.result, context);
let _ = self.output.flush();
self.clear();
}
}
fn create_end(&mut self, context: &mut CTX, _: &CreateInputs, outcome: &mut CreateOutcome) {
self.gas_inspector.create_end(outcome);
if context.journal_mut().depth() == 0 {
self.print_summary(&outcome.result, context);
let _ = self.output.flush();
self.clear();
}
}
}
fn write_value(
output: &mut dyn std::io::Write,
value: &impl serde::Serialize,
) -> std::io::Result<()> {
serde_json::to_writer(&mut *output, value)?;
output.write_all(b"\n")
}
fn serde_hex_u64<S: serde::Serializer>(n: &u64, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("{:#x}", *n))
}