use std::collections::HashMap;
use alloy::{
dyn_abi::{DynSolValue, EventExt, FunctionExt, JsonAbiExt},
json_abi::{Event, Function, JsonAbi},
primitives::{Address, LogData, Selector, B256},
};
use revm_inspectors::tracing::{
types::{CallTrace, DecodedCallData, DecodedCallLog, DecodedCallTrace},
CallTraceArena,
};
use crate::evm::traces::{
config::TraceConfig,
etherscan::EtherscanIdentifier,
signatures::{SelectorKind, SignaturesIdentifier},
};
fn format_dyn_sol_value(value: &DynSolValue) -> String {
match value {
DynSolValue::Address(addr) => addr.to_string(),
DynSolValue::Bytes(bytes) => {
if bytes.is_empty() {
"[]".to_string()
} else {
format!("0x{}", hex::encode(bytes))
}
}
DynSolValue::FixedBytes(bytes, _) => format!("0x{}", hex::encode(bytes)),
DynSolValue::Uint(val, _) => val.to_string(),
DynSolValue::Int(val, _) => val.to_string(),
DynSolValue::Bool(b) => b.to_string(),
DynSolValue::String(s) => format!("\"{}\"", s),
DynSolValue::Array(arr) => {
let formatted_items: Vec<String> = arr
.iter()
.map(format_dyn_sol_value)
.collect();
format!("[{}]", formatted_items.join(", "))
}
DynSolValue::FixedArray(arr) => {
let formatted_items: Vec<String> = arr
.iter()
.map(format_dyn_sol_value)
.collect();
format!("[{}]", formatted_items.join(", "))
}
DynSolValue::Tuple(tuple) => {
let formatted_items: Vec<String> = tuple
.iter()
.map(format_dyn_sol_value)
.collect();
format!("({})", formatted_items.join(", "))
}
DynSolValue::Function(func) => format!("function({})", hex::encode(func.as_slice())),
DynSolValue::CustomStruct { name, tuple, .. } => {
let formatted_items: Vec<String> = tuple
.iter()
.map(format_dyn_sol_value)
.collect();
format!("{}({})", name, formatted_items.join(", "))
}
}
}
pub struct CallTraceDecoder {
labels: HashMap<Address, String>,
contracts: HashMap<Address, String>,
functions: HashMap<Selector, Function>,
events: HashMap<B256, Event>,
etherscan_identifier: Option<EtherscanIdentifier>,
signature_identifier: Option<SignaturesIdentifier>,
}
impl CallTraceDecoder {
pub fn new() -> Self {
Self {
labels: HashMap::new(),
contracts: HashMap::new(),
functions: HashMap::new(),
events: HashMap::new(),
etherscan_identifier: None,
signature_identifier: None,
}
}
pub async fn with_config(
config: &TraceConfig,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let mut decoder = Self::new();
decoder
.labels
.extend(config.labels.clone());
if let Ok(Some(identifier)) = EtherscanIdentifier::new(config) {
decoder.etherscan_identifier = Some(identifier);
}
decoder.signature_identifier = Some(SignaturesIdentifier::new(config.offline)?);
Ok(decoder)
}
#[allow(dead_code)]
pub fn with_abi(mut self, abi: &JsonAbi, address: Option<Address>) -> Self {
for function in abi.functions() {
self.functions
.insert(function.selector(), function.clone());
}
for event in abi.events() {
self.events
.insert(event.selector(), event.clone());
}
if let Some(addr) = address {
if abi.constructor.is_some() {
self.contracts
.insert(addr, "Contract".to_string()); }
}
self
}
#[allow(dead_code)]
pub fn with_label(mut self, address: Address, label: String) -> Self {
self.labels.insert(address, label);
self
}
pub async fn identify_trace(
&mut self,
arena: &CallTraceArena,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let unknown_addresses: Vec<Address> = arena
.nodes()
.iter()
.map(|node| node.trace.address)
.filter(|addr| !self.labels.contains_key(addr) && !self.contracts.contains_key(addr))
.collect();
if unknown_addresses.is_empty() {
return Ok(());
}
if let Some(identifier) = &mut self.etherscan_identifier {
let identified = identifier
.identify_addresses(&unknown_addresses)
.await;
for addr_info in identified {
if let Some(label) = addr_info.label {
self.labels
.insert(addr_info.address, label);
}
if let Some(contract) = addr_info.contract {
self.contracts
.insert(addr_info.address, contract);
}
if let Some(abi) = addr_info.abi {
for function in abi.functions() {
self.functions
.insert(function.selector(), function.clone());
}
for event in abi.events() {
self.events
.insert(event.selector(), event.clone());
}
}
}
}
if let Some(sig_identifier) = &mut self.signature_identifier {
let mut selectors = Vec::new();
for node in arena.nodes() {
if node.trace.data.len() >= 4 {
let selector = Selector::from_slice(&node.trace.data[..4]);
if !self.functions.contains_key(&selector) {
selectors.push(SelectorKind::Function(selector));
}
}
for log in &node.logs {
if !log.raw_log.topics().is_empty() {
let topic = log.raw_log.topics()[0];
if !self.events.contains_key(&topic) {
selectors.push(SelectorKind::Event(topic));
}
}
}
}
sig_identifier
.identify_batch(&selectors)
.await?;
}
Ok(())
}
pub async fn decode_arena(
&mut self,
arena: &mut CallTraceArena,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.identify_trace(arena).await?;
for node in arena.nodes_mut() {
node.trace.decoded = Some(Box::new(
self.decode_call_trace(&node.trace)
.await,
));
for log in &mut node.logs {
log.decoded = Some(Box::new(self.decode_log(&log.raw_log).await));
}
}
Ok(())
}
pub async fn decode_call_trace(&mut self, trace: &CallTrace) -> DecodedCallTrace {
let label = self.labels.get(&trace.address).cloned();
if trace.kind.is_any_create() {
return DecodedCallTrace {
label,
call_data: Some(DecodedCallData {
signature: "constructor()".to_string(),
args: vec![],
}),
return_data: None,
};
}
if trace.data.len() < 4 {
return DecodedCallTrace { label, call_data: None, return_data: None };
}
let selector = Selector::from_slice(&trace.data[..4]);
let function = if let Some(func) = self.functions.get(&selector) {
Some(func.clone())
} else if let Some(sig_identifier) = &mut self.signature_identifier {
sig_identifier
.identify_function(selector)
.await
} else {
None
};
let call_data = if let Some(func) = &function {
let args = if trace.data.len() > 4 {
func.abi_decode_input(&trace.data[4..])
.map(|values| {
values
.iter()
.map(format_dyn_sol_value)
.collect()
})
.unwrap_or_default()
} else {
vec![]
};
Some(DecodedCallData { signature: func.signature(), args })
} else {
Some(DecodedCallData {
signature: format!("0x{}", hex::encode(&trace.data[..4])),
args: if trace.data.len() > 4 {
vec![hex::encode(&trace.data[4..])]
} else {
vec![]
},
})
};
let return_data = if trace.success && !trace.output.is_empty() {
if let Some(func) = &function {
func.abi_decode_output(&trace.output)
.map(|values| {
values
.iter()
.map(format_dyn_sol_value)
.collect::<Vec<_>>()
.join(", ")
})
.ok()
} else {
Some(hex::encode(&trace.output))
}
} else if !trace.success && !trace.output.is_empty() {
if trace.output.len() >= 68 && trace.output[0..4] == [0x08, 0xc3, 0x79, 0xa0] {
Some(format!("Error: 0x{}", hex::encode(&trace.output)))
} else {
Some(hex::encode(&trace.output))
}
} else {
None
};
DecodedCallTrace { label, call_data, return_data }
}
pub async fn decode_log(&mut self, log: &LogData) -> DecodedCallLog {
if log.topics().is_empty() {
return DecodedCallLog { name: None, params: None };
}
let topic = log.topics()[0];
let event = if let Some(evt) = self.events.get(&topic) {
Some(evt.clone())
} else if let Some(sig_identifier) = &mut self.signature_identifier {
sig_identifier
.identify_event(topic)
.await
} else {
None
};
if let Some(event) = &event {
if let Ok(decoded) = event.decode_log(log) {
let params: Vec<(String, String)> = decoded
.indexed
.iter()
.chain(decoded.body.iter())
.zip(event.inputs.iter())
.map(|(value, input)| (input.name.clone(), format_dyn_sol_value(value)))
.collect();
return DecodedCallLog { name: Some(event.name.clone()), params: Some(params) };
}
}
DecodedCallLog {
name: Some(format!("0x{}", hex::encode(topic.as_slice()))),
params: Some(vec![("data".to_string(), hex::encode(&log.data))]),
}
}
}