use crate::{
DispatchError, ExecReturnValue, Key, Weight,
evm::{
Bytes, ExecutionStep, ExecutionStepKind, ExecutionTrace, ExecutionTracerConfig,
tracing::Tracing,
},
tracing::{EVMFrameTraceInfo, FrameTraceInfo},
vm::pvm::env::lookup_trace_op_index,
};
use alloc::{
collections::BTreeMap,
format,
string::{String, ToString},
vec::Vec,
};
use sp_core::{H160, U256};
#[derive(Default, Debug, Clone, PartialEq)]
struct PendingStep {
step_index: usize,
child_gas: u64,
child_weight: Weight,
}
#[derive(Default, Debug, Clone, PartialEq)]
pub struct ExecutionTracer {
config: ExecutionTracerConfig,
steps: Vec<ExecutionStep>,
pending: Vec<PendingStep>,
depth: u16,
step_count: u64,
total_gas_used: u64,
base_call_weight: Weight,
weight_consumed: Weight,
failed: bool,
return_value: Bytes,
storages_per_call: Vec<BTreeMap<Bytes, Bytes>>,
}
impl ExecutionTracer {
pub fn new(config: ExecutionTracerConfig) -> Self {
Self {
config,
steps: Vec::new(),
pending: Vec::new(),
depth: 0,
step_count: 0,
total_gas_used: 0,
base_call_weight: Default::default(),
weight_consumed: Default::default(),
failed: false,
return_value: Bytes::default(),
storages_per_call: alloc::vec![Default::default()],
}
}
pub fn collect_trace(self) -> ExecutionTrace {
let Self {
steps: struct_logs,
weight_consumed,
base_call_weight,
return_value,
total_gas_used: gas,
failed,
..
} = self;
ExecutionTrace { gas, weight_consumed, base_call_weight, failed, return_value, struct_logs }
}
fn record_error(&mut self, error: String) {
if let Some(last_step) = self.steps.last_mut() {
last_step.error = Some(error);
}
}
}
impl Tracing for ExecutionTracer {
fn is_execution_tracer(&self) -> bool {
true
}
fn dispatch_result(&mut self, base_call_weight: Weight, weight_consumed: Weight) {
self.base_call_weight = base_call_weight;
self.weight_consumed = weight_consumed;
}
fn enter_opcode(&mut self, pc: u64, opcode: u8, trace_info: &dyn EVMFrameTraceInfo) {
if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) {
return;
}
let stack_data =
if !self.config.disable_stack { trace_info.stack_snapshot() } else { Vec::new() };
let memory_data = if self.config.enable_memory {
trace_info.memory_snapshot(self.config.memory_word_limit as usize)
} else {
Vec::new()
};
let return_data = if self.config.enable_return_data {
trace_info.last_frame_output()
} else {
crate::evm::Bytes::default()
};
let step = ExecutionStep {
gas: trace_info.gas_left(),
gas_cost: Default::default(),
weight_cost: trace_info.weight_consumed(),
depth: self.depth,
return_data,
error: None,
kind: ExecutionStepKind::EVMOpcode {
pc: pc as u32,
op: opcode,
stack: stack_data,
memory: memory_data,
storage: None,
},
};
let step_index = self.steps.len();
self.steps.push(step);
self.pending
.push(PendingStep { step_index, child_gas: 0, child_weight: Weight::zero() });
self.step_count += 1;
}
fn enter_ecall(&mut self, ecall: &'static str, args: &[u64], trace_info: &dyn FrameTraceInfo) {
if self.config.limit.map(|l| self.step_count >= l).unwrap_or(false) {
return;
}
let return_data = if self.config.enable_return_data {
trace_info.last_frame_output()
} else {
crate::evm::Bytes::default()
};
let syscall_args =
if !self.config.disable_syscall_details { args.to_vec() } else { Vec::new() };
let step = ExecutionStep {
gas: trace_info.gas_left(),
gas_cost: Default::default(),
weight_cost: trace_info.weight_consumed(),
depth: self.depth,
return_data,
error: None,
kind: ExecutionStepKind::PVMSyscall {
op: lookup_trace_op_index(ecall).unwrap_or_default(),
args: syscall_args,
returned: None,
},
};
let step_index = self.steps.len();
self.steps.push(step);
self.pending
.push(PendingStep { step_index, child_gas: 0, child_weight: Weight::zero() });
self.step_count += 1;
}
fn exit_step(&mut self, trace_info: &dyn FrameTraceInfo, returned: Option<u64>) {
let Some(pending) = self.pending.pop() else { return };
let Some(step) = self.steps.get_mut(pending.step_index) else { return };
let total_gas = step.gas.saturating_sub(trace_info.gas_left());
step.gas_cost = total_gas.saturating_sub(pending.child_gas);
let total_weight = trace_info.weight_consumed().saturating_sub(step.weight_cost);
step.weight_cost = total_weight.saturating_sub(pending.child_weight);
if !self.config.disable_syscall_details &&
let ExecutionStepKind::PVMSyscall { returned: ref mut ret, .. } = step.kind
{
*ret = returned;
}
}
fn enter_child_span(
&mut self,
_from: H160,
_to: H160,
_delegate_call: Option<H160>,
_is_read_only: bool,
_value: U256,
_input: &[u8],
_gas_limit: u64,
) {
self.storages_per_call.push(Default::default());
self.depth += 1;
}
fn exit_child_span(
&mut self,
output: &ExecReturnValue,
gas_used: u64,
weight_consumed: Weight,
) {
if let Some(parent) = self.pending.last_mut() {
parent.child_gas = parent.child_gas.saturating_add(gas_used);
parent.child_weight = parent.child_weight.saturating_add(weight_consumed);
}
if output.did_revert() {
self.record_error("execution reverted".to_string());
if self.depth == 0 {
self.failed = true;
}
} else {
self.return_value = Bytes(output.data.to_vec());
}
if self.depth == 1 {
self.total_gas_used = gas_used;
}
self.storages_per_call.pop();
if self.depth > 0 {
self.depth -= 1;
}
}
fn exit_child_span_with_error(
&mut self,
error: DispatchError,
gas_used: u64,
weight_consumed: Weight,
) {
if let Some(parent) = self.pending.last_mut() {
parent.child_gas = parent.child_gas.saturating_add(gas_used);
parent.child_weight = parent.child_weight.saturating_add(weight_consumed);
}
self.record_error(format!("{:?}", error));
if self.depth == 1 {
self.failed = true;
self.total_gas_used = gas_used;
}
if self.depth > 0 {
self.depth -= 1;
}
self.storages_per_call.pop();
}
fn storage_write(&mut self, key: &Key, _old_value: Option<Vec<u8>>, new_value: Option<&[u8]>) {
if self.config.disable_storage {
return;
}
if let Some(storage) = self.storages_per_call.last_mut() {
let key_bytes = crate::evm::Bytes(key.unhashed().to_vec());
let value_bytes = crate::evm::Bytes(
new_value.map(|v| v.to_vec()).unwrap_or_else(|| alloc::vec![0u8; 32]),
);
storage.insert(key_bytes, value_bytes);
if let Some(step) = self.steps.last_mut() {
if let ExecutionStepKind::EVMOpcode { storage: ref mut step_storage, .. } =
step.kind
{
*step_storage = Some(storage.clone());
}
}
}
}
fn storage_read(&mut self, key: &Key, value: Option<&[u8]>) {
if self.config.disable_storage {
return;
}
if let Some(storage) = self.storages_per_call.last_mut() {
let key_bytes = crate::evm::Bytes(key.unhashed().to_vec());
storage.entry(key_bytes).or_insert_with(|| {
crate::evm::Bytes(value.map(|v| v.to_vec()).unwrap_or_else(|| alloc::vec![0u8; 32]))
});
if let Some(step) = self.steps.last_mut() {
if let ExecutionStepKind::EVMOpcode { storage: ref mut step_storage, .. } =
step.kind
{
*step_storage = Some(storage.clone());
}
}
}
}
}