anvil_zksync_types/traces.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
use lazy_static::lazy_static;
use serde::Deserialize;
use std::collections::HashMap;
use zksync_multivm::interface::{Call, ExecutionResult, VmEvent, VmExecutionResultAndLogs};
use zksync_types::{
l2_to_l1_log::{SystemL2ToL1Log, UserL2ToL1Log},
web3::Bytes,
Address, H160, H256,
};
/// Enum to represent both user and system L1-L2 logs
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum L2L1Log {
User(UserL2ToL1Log),
System(SystemL2ToL1Log),
}
// TODO: duplicated types from existing formatter.rs
// will be consolidated pending feedback
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub enum ContractType {
System,
Precompile,
Popular,
Unknown,
}
#[derive(Debug, Deserialize, Clone)]
pub struct KnownAddress {
pub address: H160,
pub name: String,
contract_type: ContractType,
}
lazy_static! {
/// Loads the known contact addresses from the JSON file.
pub static ref KNOWN_ADDRESSES: HashMap<H160, KnownAddress> = {
let json_value = serde_json::from_slice(include_bytes!("./data/address_map.json")).unwrap();
let pairs: Vec<KnownAddress> = serde_json::from_value(json_value).unwrap();
pairs
.into_iter()
.map(|entry| (entry.address, entry))
.collect()
};
}
/// A ZKsync event log object.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct LogData {
/// The indexed topic list.
topics: Vec<H256>,
/// The plain data.
pub data: Bytes,
}
impl LogData {
/// Creates a new log, without length-checking. This allows creation of
/// invalid logs. May be safely used when the length of the topic list is
/// known to be 4 or less.
#[inline]
pub const fn new_unchecked(topics: Vec<H256>, data: Bytes) -> Self {
Self { topics, data }
}
/// Creates a new log.
#[inline]
pub fn new(topics: Vec<H256>, data: Bytes) -> Option<Self> {
let this = Self::new_unchecked(topics, data);
this.is_valid().then_some(this)
}
/// Creates a new empty log.
#[inline]
pub const fn empty() -> Self {
Self {
topics: Vec::new(),
data: Bytes(Vec::new()),
}
}
/// True if valid, false otherwise.
#[inline]
pub fn is_valid(&self) -> bool {
self.topics.len() <= 4
}
/// Get the topic list.
#[inline]
pub fn topics(&self) -> &[H256] {
&self.topics
}
/// Get the topic list, mutably. This gives access to the internal
/// array, without allowing extension of that array.
#[inline]
pub fn topics_mut(&mut self) -> &mut [H256] {
&mut self.topics
}
/// Get a mutable reference to the topic list. This allows creation of
/// invalid logs.
#[inline]
pub fn topics_mut_unchecked(&mut self) -> &mut Vec<H256> {
&mut self.topics
}
/// Set the topic list, without length-checking. This allows creation of
/// invalid logs.
#[inline]
pub fn set_topics_unchecked(&mut self, topics: Vec<H256>) {
self.topics = topics;
}
/// Set the topic list, truncating to 4 topics.
#[inline]
pub fn set_topics_truncating(&mut self, mut topics: Vec<H256>) {
topics.truncate(4);
self.set_topics_unchecked(topics);
}
/// Consumes the log data, returning the topic list and the data.
#[inline]
pub fn split(self) -> (Vec<H256>, Bytes) {
(self.topics, self.data)
}
}
/// Decoded call data.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DecodedCallData {
/// The function signature.
pub signature: String,
/// The function arguments.
pub args: Vec<String>,
}
/// Additional decoded data enhancing the [CallTrace].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DecodedCallTrace {
/// Optional decoded label for the call.
pub label: Option<String>,
/// Optional decoded return data.
pub return_data: Option<String>,
/// Optional decoded call data.
pub call_data: Option<DecodedCallData>,
}
/// Additional decoded data enhancing the [CallLog].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DecodedCallLog {
/// The decoded event name.
pub name: Option<String>,
/// The decoded log parameters, a vector of the parameter name (e.g. foo) and the parameter
/// value (e.g. 0x9d3...45ca).
pub params: Option<Vec<(String, String)>>,
}
/// A log with optional decoded data.
#[derive(Clone, Debug, Default)]
pub struct CallLog {
/// The raw log data.
pub raw_log: VmEvent,
/// Optional complementary decoded log data.
pub decoded: DecodedCallEvent,
/// The position of the log relative to subcalls within the same trace.
pub position: u64,
}
/// A log with optional decoded data.
#[derive(Clone, Debug)]
pub struct L2L1Logs {
/// The raw log data.
pub raw_log: L2L1Log,
/// The position of the log relative to subcalls within the same trace.
pub position: u64,
}
impl CallLog {
/// Sets the position of the log.
#[inline]
pub fn with_position(mut self, position: u64) -> Self {
self.position = position;
self
}
}
/// A trace of a call with optional decoded data.
#[derive(Clone, Debug)]
pub struct CallTrace {
/// The depth of the call.
pub depth: usize,
/// Whether the call was successful.
pub success: bool,
/// The caller address.
pub caller: Address,
/// The target address of this call.
///
/// This is:
/// - [`CallKind::Call`] and alike: the callee, the address of the contract being called
/// - [`CallKind::Create`] and alike: the address of the created contract
pub address: Address,
/// The execution result of the call.
pub execution_result: VmExecutionResultAndLogs,
/// Optional complementary decoded call data.
pub decoded: DecodedCallTrace,
/// The call trace
pub call: Call,
}
/// Decoded ZKSync event data enhancing the [CallLog].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DecodedCallEvent {
/// The decoded event name.
pub name: Option<String>,
/// The decoded log parameters, a vector of the parameter name (e.g. foo) and the parameter
/// value (e.g. 0x9d3...45ca).
pub params: Option<Vec<(String, String)>>,
}
/// A node in the arena
#[derive(Clone, Debug)]
pub struct CallTraceNode {
/// Parent node index in the arena
pub parent: Option<usize>,
/// Children node indexes in the arena
pub children: Vec<usize>,
/// This node's index in the arena
pub idx: usize,
/// The call trace
pub trace: CallTrace,
/// Event logs
pub logs: Vec<CallLog>,
/// L2-L1 logs
pub l2_l1_logs: Vec<L2L1Logs>,
/// Ordering of child calls and logs
pub ordering: Vec<TraceMemberOrder>,
}
impl Default for CallTraceNode {
fn default() -> Self {
Self {
parent: None,
children: Vec::new(),
idx: 0,
trace: CallTrace {
depth: 0,
success: true,
caller: H160::zero(),
address: H160::zero(),
execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success {
output: vec![],
}),
decoded: DecodedCallTrace::default(),
call: Call::default(),
},
logs: Vec::new(),
l2_l1_logs: Vec::new(),
ordering: Vec::new(),
}
}
}
/// Ordering enum for calls, logs and steps
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TraceMemberOrder {
/// Contains the index of the corresponding log
Log(usize),
/// Contains the index of the corresponding trace node
Call(usize),
/// Contains the index of the corresponding l1-l2 log
L1L2Log(usize),
}
/// An arena of recorded traces.
///
/// This type will be populated via the [TracingInspector](super::TracingInspector).
#[derive(Clone, Debug)]
pub struct CallTraceArena {
/// The arena of recorded trace nodes
pub arena: Vec<CallTraceNode>,
}
impl Default for CallTraceArena {
fn default() -> Self {
let root_node = CallTraceNode {
parent: None,
children: Vec::new(),
idx: 0,
trace: CallTrace {
depth: 0,
success: true,
caller: H160::zero(),
address: H160::zero(),
execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success {
output: vec![],
}),
decoded: DecodedCallTrace::default(),
call: Call::default(),
},
logs: Vec::new(),
l2_l1_logs: Vec::new(),
ordering: Vec::new(),
};
// Initialize CallTraceArena with the root node
Self {
arena: vec![root_node],
}
}
}
impl CallTraceArena {
/// Adds a node to the arena, updating parent–child relationships and ordering.
pub fn add_node(&mut self, parent: Option<usize>, mut node: CallTraceNode) -> usize {
let idx = self.arena.len();
node.idx = idx;
node.parent = parent;
// Build ordering for the node based on its logs.
node.ordering = (0..node.logs.len()).map(TraceMemberOrder::Log).collect();
self.arena.push(node);
// Update parent's children and ordering if a parent exists.
if let Some(parent_idx) = parent {
self.arena[parent_idx].children.push(idx);
let child_local_idx = self.arena[parent_idx].children.len() - 1;
self.arena[parent_idx]
.ordering
.push(TraceMemberOrder::Call(child_local_idx));
}
idx
}
/// Returns the nodes in the arena.
pub fn nodes(&self) -> &[CallTraceNode] {
&self.arena
}
/// Returns a mutable reference to the nodes in the arena.
pub fn nodes_mut(&mut self) -> &mut Vec<CallTraceNode> {
&mut self.arena
}
/// Consumes the arena and returns the nodes.
pub fn into_nodes(self) -> Vec<CallTraceNode> {
self.arena
}
/// Clears the arena
///
/// Note that this method has no effect on the allocated capacity of the arena.
#[inline]
pub fn clear(&mut self) {
self.arena.clear();
self.arena.push(Default::default());
}
/// Checks if the given address is a precompile based on `KNOWN_ADDRESSES`.
pub fn is_precompile(address: &Address) -> bool {
if let Some(known) = KNOWN_ADDRESSES.get(address) {
matches!(known.contract_type, ContractType::Precompile)
} else {
false
}
}
/// Filters out precompile nodes from the arena.
pub fn filter_out_precompiles(&mut self) {
self.arena
.retain(|node| !Self::is_precompile(&node.trace.address));
}
/// Checks if the given address is a system contract based on `KNOWN_ADDRESSES`.
pub fn is_system(address: &Address) -> bool {
if let Some(known) = KNOWN_ADDRESSES.get(address) {
matches!(known.contract_type, ContractType::System)
} else {
false
}
}
/// Filters out system contracts nodes from the arena.
pub fn filter_out_system_contracts(&mut self) {
self.arena
.retain(|node| !Self::is_system(&node.trace.address));
}
}
/// A trait for displaying the execution result.
pub trait ExecutionResultDisplay {
fn display(&self) -> String;
}
impl ExecutionResultDisplay for ExecutionResult {
fn display(&self) -> String {
match self {
ExecutionResult::Success { .. } => "Success".to_string(),
ExecutionResult::Revert { output } => format!("Revert: {}", output),
ExecutionResult::Halt { reason } => format!("Halt: {:?}", reason),
}
}
}