use crate::{
evm::{decode_revert_reason, CallLog, CallTrace, CallTracerConfig, CallType},
primitives::ExecReturnValue,
tracing::Tracing,
Code, DispatchError,
};
use alloc::{format, string::ToString, vec::Vec};
use sp_core::{H160, H256, U256};
#[derive(Default, Debug, Clone, PartialEq)]
pub struct CallTracer {
traces: Vec<CallTrace<U256>>,
current_stack: Vec<usize>,
code_with_salt: Option<(Code, bool)>,
config: CallTracerConfig,
}
impl CallTracer {
pub fn new(config: CallTracerConfig) -> Self {
Self { traces: Vec::new(), code_with_salt: None, current_stack: Vec::new(), config }
}
pub fn collect_trace(mut self) -> Option<CallTrace> {
self.traces.pop()
}
}
impl Tracing for CallTracer {
fn instantiate_code(&mut self, code: &Code, salt: Option<&[u8; 32]>) {
self.code_with_salt = Some((code.clone(), salt.is_some()));
}
fn terminate(
&mut self,
contract_address: H160,
beneficiary_address: H160,
gas_left: U256,
value: U256,
) {
self.traces.last_mut().unwrap().calls.push(CallTrace {
from: contract_address,
to: beneficiary_address,
call_type: CallType::Selfdestruct,
gas: gas_left,
value: Some(value),
..Default::default()
});
}
fn enter_child_span(
&mut self,
from: H160,
to: H160,
delegate_call: Option<H160>,
is_read_only: bool,
value: U256,
input: &[u8],
gas_limit: U256,
) {
if let Some(&index) = self.current_stack.last() {
if let Some(trace) = self.traces.get_mut(index) {
trace.child_call_count += 1;
}
}
if self.traces.is_empty() || !self.config.only_top_call {
let (call_type, input) = match self.code_with_salt.take() {
Some((Code::Upload(code), salt)) => (
if salt { CallType::Create2 } else { CallType::Create },
code.into_iter().chain(input.to_vec().into_iter()).collect::<Vec<_>>(),
),
Some((Code::Existing(code_hash), salt)) => (
if salt { CallType::Create2 } else { CallType::Create },
code_hash
.to_fixed_bytes()
.into_iter()
.chain(input.to_vec().into_iter())
.collect::<Vec<_>>(),
),
None => {
let call_type = if is_read_only {
CallType::StaticCall
} else if delegate_call.is_some() {
CallType::DelegateCall
} else {
CallType::Call
};
(call_type, input.to_vec())
},
};
self.traces.push(CallTrace {
from,
to,
value: if is_read_only { None } else { Some(value) },
call_type,
input: input.into(),
gas: gas_limit,
..Default::default()
});
self.current_stack.push(self.traces.len() - 1);
} else {
self.current_stack.push(2);
}
}
fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) {
if !self.config.with_logs {
return;
}
let current_index = self.current_stack.last().unwrap();
if let Some(trace) = self.traces.get_mut(*current_index) {
let log = CallLog {
address,
topics: topics.to_vec(),
data: data.to_vec().into(),
position: trace.child_call_count,
};
trace.logs.push(log);
}
}
fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: U256) {
self.code_with_salt = None;
let current_index = self.current_stack.pop().unwrap();
if let Some(trace) = self.traces.get_mut(current_index) {
trace.output = output.data.clone().into();
trace.gas_used = gas_used;
if output.did_revert() {
trace.revert_reason = decode_revert_reason(&output.data);
trace.error = Some("execution reverted".to_string());
}
if self.config.only_top_call {
return
}
if let Some(parent_index) = self.current_stack.last() {
let child_trace = self.traces.remove(current_index);
self.traces[*parent_index].calls.push(child_trace);
}
}
}
fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: U256) {
self.code_with_salt = None;
let current_index = self.current_stack.pop().unwrap();
if let Some(trace) = self.traces.get_mut(current_index) {
trace.gas_used = gas_used;
trace.error = match error {
DispatchError::Module(sp_runtime::ModuleError { message, .. }) =>
Some(message.unwrap_or_default().to_string()),
_ => Some(format!("{:?}", error)),
};
if self.config.only_top_call {
return
}
if let Some(parent_index) = self.current_stack.last() {
let child_trace = self.traces.remove(current_index);
self.traces[*parent_index].calls.push(child_trace);
}
}
}
}