anvil_zksync_types/
traces.rs

1use anvil_zksync_common::{address_map, utils::format::write_interspersed};
2use zksync_multivm::interface::{Call, ExecutionResult, VmEvent};
3use zksync_types::{
4    l2_to_l1_log::{SystemL2ToL1Log, UserL2ToL1Log},
5    web3::Bytes,
6    Address, H160, H256, U256,
7};
8
9use crate::numbers::SignedU256;
10
11/// Enum to represent both user and system L1-L2 logs
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum L2L1Log {
14    User(UserL2ToL1Log),
15    System(SystemL2ToL1Log),
16}
17
18/// A ZKsync event log object. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
19pub struct LogData {
20    /// The indexed topic list.
21    topics: Vec<H256>,
22    /// The plain data.
23    pub data: Bytes,
24}
25
26impl LogData {
27    /// Creates a new log, without length-checking. This allows creation of
28    /// invalid logs. May be safely used when the length of the topic list is
29    /// known to be 4 or less.
30    #[inline]
31    pub const fn new_unchecked(topics: Vec<H256>, data: Bytes) -> Self {
32        Self { topics, data }
33    }
34
35    /// Creates a new log.
36    #[inline]
37    pub fn new(topics: Vec<H256>, data: Bytes) -> Option<Self> {
38        let this = Self::new_unchecked(topics, data);
39        this.is_valid().then_some(this)
40    }
41
42    /// Creates a new empty log.
43    #[inline]
44    pub const fn empty() -> Self {
45        Self {
46            topics: Vec::new(),
47            data: Bytes(Vec::new()),
48        }
49    }
50
51    /// True if valid, false otherwise.
52    #[inline]
53    pub fn is_valid(&self) -> bool {
54        self.topics.len() <= 4
55    }
56
57    /// Get the topic list.
58    #[inline]
59    pub fn topics(&self) -> &[H256] {
60        &self.topics
61    }
62
63    /// Get the topic list, mutably. This gives access to the internal
64    /// array, without allowing extension of that array.
65    #[inline]
66    pub fn topics_mut(&mut self) -> &mut [H256] {
67        &mut self.topics
68    }
69
70    /// Get a mutable reference to the topic list. This allows creation of
71    /// invalid logs.
72    #[inline]
73    pub fn topics_mut_unchecked(&mut self) -> &mut Vec<H256> {
74        &mut self.topics
75    }
76
77    /// Set the topic list, without length-checking. This allows creation of
78    /// invalid logs.
79    #[inline]
80    pub fn set_topics_unchecked(&mut self, topics: Vec<H256>) {
81        self.topics = topics;
82    }
83
84    /// Set the topic list, truncating to 4 topics.
85    #[inline]
86    pub fn set_topics_truncating(&mut self, mut topics: Vec<H256>) {
87        topics.truncate(4);
88        self.set_topics_unchecked(topics);
89    }
90
91    /// Consumes the log data, returning the topic list and the data.
92    #[inline]
93    pub fn split(self) -> (Vec<H256>, Bytes) {
94        (self.topics, self.data)
95    }
96}
97
98pub type Label = String;
99pub type Word32 = [u8; 32];
100pub type Word24 = [u8; 24];
101
102#[derive(Clone, Debug, Default, Eq, PartialEq)]
103pub struct LabeledAddress {
104    pub label: Option<Label>,
105    pub address: Address,
106}
107
108/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
109// Attribution: the type `DecodedValue` was adapted                                                            //
110// from the type `alloy::dyn_abi::DynSolValue` of the crate `alloy_dyn_abi`                                    //
111//                                                                                                             //
112// Full credit goes to its authors. See the original implementation here:                                      //
113// https://github.com/alloy-rs/core/blob/main/crates/dyn-abi/src/dynamic/value.rs                              //
114//                                                                                                             //
115// Note: This type is used under the terms of the original project's license.                                  //
116/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
117///
118/// A decoded value in trace.
119///
120#[derive(Clone, Debug, Eq, PartialEq)]
121pub enum DecodedValue {
122    /// A boolean.
123    Bool(bool),
124    /// A signed integer. The second parameter is the number of bits, not bytes.
125    Int(SignedU256),
126    /// An unsigned integer. The second parameter is the number of bits, not bytes.
127    Uint(U256),
128    /// A fixed-length byte array. The second parameter is the number of bytes.
129    FixedBytes(Word32, usize),
130    /// An address.
131    Address(LabeledAddress),
132    /// A function pointer.
133    Function(Word24),
134
135    /// A dynamic-length byte array.
136    Bytes(Vec<u8>),
137    /// A string.
138    String(String),
139
140    /// A dynamically-sized array of values.
141    Array(Vec<DecodedValue>),
142    /// A fixed-size array of values.
143    FixedArray(Vec<DecodedValue>),
144    /// A tuple of values.
145    Tuple(Vec<DecodedValue>),
146
147    /// A named struct, treated as a tuple with a name parameter.
148    CustomStruct {
149        /// The name of the struct.
150        name: String,
151        /// The struct's prop names, in declaration order.
152        prop_names: Vec<String>,
153        /// The inner types.
154        tuple: Vec<DecodedValue>,
155    },
156}
157
158impl std::fmt::Display for LabeledAddress {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        let LabeledAddress { label, address } = self;
161
162        if let Some(label) = label {
163            f.write_fmt(format_args!("{label}: "))?;
164        }
165        write!(f, "[0x{}]", hex::encode(address))
166    }
167}
168
169impl std::fmt::Display for DecodedValue {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        match self {
172            DecodedValue::Bool(inner) => inner.fmt(f),
173            DecodedValue::Int(inner) => inner.fmt(f),
174            DecodedValue::Uint(inner) => inner.fmt(f),
175            DecodedValue::FixedBytes(word, size) => {
176                f.write_fmt(format_args!("0x{}", hex::encode(&word[..*size])))
177            }
178            DecodedValue::Address(labeled_address) => labeled_address.fmt(f),
179            DecodedValue::Function(inner) => f.write_fmt(format_args!("0x{}", hex::encode(inner))),
180            DecodedValue::Bytes(inner) => f.write_fmt(format_args!("0x{}", hex::encode(inner))),
181            DecodedValue::String(inner) => f.write_str(&inner.escape_debug().to_string()),
182            DecodedValue::Array(vec) | DecodedValue::FixedArray(vec) => {
183                f.write_str("[")?;
184                write_interspersed(f, vec.iter(), ", ")?;
185                f.write_str("]")
186            }
187            DecodedValue::Tuple(vec) => {
188                f.write_str("(")?;
189                write_interspersed(f, vec.iter(), ", ")?;
190                f.write_str(")")
191            }
192            DecodedValue::CustomStruct { tuple, .. } => DecodedValue::Tuple(tuple.clone()).fmt(f),
193        }
194    }
195}
196
197impl DecodedValue {
198    pub fn as_bytes(&self) -> Option<&Vec<u8>> {
199        if let Self::Bytes(v) = self {
200            Some(v)
201        } else {
202            None
203        }
204    }
205}
206
207impl std::fmt::Display for DecodedError {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        match self {
210            DecodedError::Empty => write!(f, ""),
211            DecodedError::CustomError { name, fields } => {
212                write!(f, "{name}(")?;
213                write_interspersed(f, fields.iter(), ", ")?;
214                write!(f, ")")
215            }
216            DecodedError::GenericCustomError { selector, raw } => {
217                write!(
218                    f,
219                    "custom error with function selector 0x{}",
220                    hex::encode(selector)
221                )?;
222                if !raw.is_empty() {
223                    write!(f, ": ")?;
224                    match std::str::from_utf8(raw) {
225                        Ok(data) => write!(f, "{}", data),
226                        Err(_) => write!(f, "{}", hex::encode(raw)),
227                    }
228                } else {
229                    Ok(())
230                }
231            }
232            DecodedError::Revert(message) => write!(f, "{}", message),
233            DecodedError::Panic(message) => write!(f, "{}", message),
234            DecodedError::Raw(data) => {
235                if !data.is_empty() {
236                    let var_name = write!(
237                        f,
238                        "{}",
239                        anvil_zksync_common::utils::format::trimmed_hex(data)
240                    );
241                    var_name?;
242                } else {
243                    write!(f, "<empty revert data>")?;
244                }
245                Ok(())
246            }
247            DecodedError::String(message) => write!(f, "{}", message),
248        }
249    }
250}
251
252impl std::fmt::Display for DecodedRevertData {
253    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254        match self {
255            DecodedRevertData::Value(value) => value.fmt(f),
256            DecodedRevertData::Error(error) => error.fmt(f),
257        }
258    }
259}
260#[derive(Clone, Debug, PartialEq, Eq)]
261pub enum DecodedReturnData {
262    NormalReturn(Vec<DecodedValue>),
263    Revert(DecodedRevertData),
264}
265
266impl Default for DecodedReturnData {
267    fn default() -> Self {
268        Self::NormalReturn(vec![])
269    }
270}
271
272impl std::fmt::Display for DecodedReturnData {
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        match self {
275            DecodedReturnData::NormalReturn(values) => {
276                if values.is_empty() {
277                    Ok(())
278                } else {
279                    write_interspersed(f, values.iter(), ", ")
280                }
281            }
282            DecodedReturnData::Revert(revert_data) => revert_data.fmt(f),
283        }
284    }
285}
286
287#[derive(Clone, Debug, PartialEq, Eq)]
288pub enum DecodedError {
289    Empty,
290    CustomError {
291        name: String,
292        fields: Vec<DecodedValue>,
293    },
294    GenericCustomError {
295        selector: [u8; 4],
296        raw: Vec<u8>,
297    },
298    Revert(String),
299    Panic(String),
300    Raw(Vec<u8>),
301    String(String),
302}
303
304#[derive(Clone, Debug, PartialEq, Eq)]
305pub enum DecodedRevertData {
306    Value(DecodedValue),
307    Error(DecodedError),
308}
309
310/// Decoded call data.
311#[derive(Clone, Debug, Default, PartialEq, Eq)]
312pub struct DecodedCallData {
313    /// The function signature.
314    pub signature: String,
315    /// The function arguments.
316    pub args: Vec<DecodedValue>,
317}
318
319/// Additional decoded data enhancing the [CallTrace].
320#[derive(Clone, Debug, Default, PartialEq, Eq)]
321pub struct DecodedCallTrace {
322    /// Optional decoded label for the call.
323    pub label: Option<Label>,
324    /// Optional decoded return data.
325    pub return_data: DecodedReturnData,
326    /// Optional decoded call data.
327    pub call_data: Option<DecodedCallData>,
328}
329
330/// Additional decoded data enhancing the [CallLog].
331#[derive(Clone, Debug, Default, PartialEq, Eq)]
332pub struct DecodedCallLog {
333    /// The decoded event name.
334    pub name: Option<String>,
335    /// The decoded log parameters, a vector of the parameter name (e.g. foo) and the parameter
336    /// value (e.g. 0x9d3...45ca).
337    pub params: Option<Vec<(String, DecodedValue)>>,
338}
339
340/// A log with optional decoded data.
341#[derive(Clone, Debug, Default)]
342pub struct CallLog {
343    /// The raw log data.
344    pub raw_log: VmEvent,
345    /// Optional complementary decoded log data.
346    pub decoded: DecodedCallEvent,
347    /// The position of the log relative to subcalls within the same trace.
348    pub position: u64,
349}
350
351/// A log with optional decoded data.
352#[derive(Clone, Debug)]
353pub struct L2L1Logs {
354    /// The raw log data.
355    pub raw_log: L2L1Log,
356    /// The position of the log relative to subcalls within the same trace.
357    pub position: u64,
358}
359
360impl CallLog {
361    /// Sets the position of the log.
362    #[inline]
363    pub fn with_position(mut self, position: u64) -> Self {
364        self.position = position;
365        self
366    }
367}
368
369/// A trace of a call with optional decoded data.
370#[derive(Clone, Debug)]
371pub struct CallTrace {
372    /// Whether the call was successful.
373    pub success: bool,
374    /// The caller address.
375    pub caller: Address,
376    /// The target address of this call.
377    ///
378    /// This is:
379    /// - [`CallKind::Call`] and alike: the callee, the address of the contract being called
380    /// - [`CallKind::Create`] and alike: the address of the created contract
381    pub address: Address,
382    /// The execution result of the call.
383    pub execution_result: ExecutionResult,
384    /// Optional complementary decoded call data.
385    pub decoded: DecodedCallTrace,
386    /// The call trace
387    pub call: Call,
388}
389
390/// Decoded ZKSync event data enhancing the [CallLog].
391#[derive(Clone, Debug, Default, PartialEq, Eq)]
392pub struct DecodedCallEvent {
393    /// The decoded event name.
394    pub name: Option<String>,
395    /// The decoded log parameters, a vector of the parameter name (e.g. foo) and the parameter
396    /// value (e.g. 0x9d3...45ca).
397    pub params: Option<Vec<(String, DecodedValue)>>,
398}
399
400/// A node in the arena
401#[derive(Clone, Debug)]
402pub struct CallTraceNode {
403    /// Parent node index in the arena
404    pub parent: Option<usize>,
405    /// Children node indexes in the arena
406    pub children: Vec<usize>,
407    /// This node's index in the arena
408    pub idx: usize,
409    /// The call trace
410    pub trace: CallTrace,
411    /// Event logs
412    pub logs: Vec<CallLog>,
413    /// L2-L1 logs
414    pub l2_l1_logs: Vec<L2L1Logs>,
415    /// Ordering of child calls and logs
416    pub ordering: Vec<TraceMemberOrder>,
417}
418
419impl Default for CallTraceNode {
420    fn default() -> Self {
421        Self {
422            parent: None,
423            children: Vec::new(),
424            idx: 0,
425            trace: CallTrace {
426                success: true,
427                caller: H160::zero(),
428                address: H160::zero(),
429                execution_result: ExecutionResult::Success { output: vec![] },
430                decoded: DecodedCallTrace::default(),
431                call: Call::default(),
432            },
433            logs: Vec::new(),
434            l2_l1_logs: Vec::new(),
435            ordering: Vec::new(),
436        }
437    }
438}
439
440/// Ordering enum for calls, logs and steps
441#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442pub enum TraceMemberOrder {
443    /// Contains the index of the corresponding log
444    Log(usize),
445    /// Contains the index of the corresponding trace node
446    Call(usize),
447    /// Contains the index of the corresponding l1-l2 log
448    L1L2Log(usize),
449}
450
451/// An arena of recorded traces.
452///
453/// This type will be populated via the [TracingInspector](super::TracingInspector).
454#[derive(Clone, Debug)]
455pub struct CallTraceArena {
456    /// The arena of recorded trace nodes
457    pub arena: Vec<CallTraceNode>,
458}
459
460impl Default for CallTraceArena {
461    fn default() -> Self {
462        let root_node = CallTraceNode {
463            parent: None,
464            children: Vec::new(),
465            idx: 0,
466            trace: CallTrace {
467                success: true,
468                caller: H160::zero(),
469                address: H160::zero(),
470                execution_result: ExecutionResult::Success { output: vec![] },
471                decoded: DecodedCallTrace::default(),
472                call: Call::default(),
473            },
474            logs: Vec::new(),
475            l2_l1_logs: Vec::new(),
476            ordering: Vec::new(),
477        };
478
479        // Initialize CallTraceArena with the root node
480        Self {
481            arena: vec![root_node],
482        }
483    }
484}
485
486impl CallTraceArena {
487    /// Adds a node to the arena, updating parent–child relationships and ordering.
488    pub fn add_node(&mut self, parent: Option<usize>, mut node: CallTraceNode) -> usize {
489        let idx = self.arena.len();
490        node.idx = idx;
491        node.parent = parent;
492
493        // Build ordering for the node based on its logs.
494        node.ordering = (0..node.logs.len()).map(TraceMemberOrder::Log).collect();
495
496        self.arena.push(node);
497
498        // Update parent's children and ordering if a parent exists.
499        if let Some(parent_idx) = parent {
500            self.arena[parent_idx].children.push(idx);
501            let child_local_idx = self.arena[parent_idx].children.len() - 1;
502            self.arena[parent_idx]
503                .ordering
504                .push(TraceMemberOrder::Call(child_local_idx));
505        }
506        idx
507    }
508
509    /// Returns the nodes in the arena.
510    pub fn nodes(&self) -> &[CallTraceNode] {
511        &self.arena
512    }
513
514    /// Returns a mutable reference to the nodes in the arena.
515    pub fn nodes_mut(&mut self) -> &mut Vec<CallTraceNode> {
516        &mut self.arena
517    }
518
519    /// Consumes the arena and returns the nodes.
520    pub fn into_nodes(self) -> Vec<CallTraceNode> {
521        self.arena
522    }
523
524    /// Clears the arena
525    ///
526    /// Note that this method has no effect on the allocated capacity of the arena.
527    #[inline]
528    pub fn clear(&mut self) {
529        self.arena.clear();
530        self.arena.push(Default::default());
531    }
532
533    /// Filters out precompile nodes from the arena.
534    pub fn filter_out_precompiles(&mut self) {
535        self.arena
536            .retain(|node| !address_map::is_precompile(&node.trace.address));
537    }
538
539    /// Filters out system contracts nodes from the arena.
540    pub fn filter_out_system_contracts(&mut self) {
541        self.arena
542            .retain(|node| !address_map::is_system(&node.trace.address));
543    }
544}
545
546/// A trait for displaying the execution result.
547pub trait ExecutionResultDisplay {
548    fn display(&self) -> String;
549}
550
551impl ExecutionResultDisplay for ExecutionResult {
552    fn display(&self) -> String {
553        match self {
554            ExecutionResult::Success { .. } => "Success".to_string(),
555            ExecutionResult::Revert { output } => format!("Revert: {}", output),
556            ExecutionResult::Halt { reason } => format!("Halt: {:?}", reason),
557        }
558    }
559}