edb_common/types/
trace.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17use alloy_primitives::{hex, Address, Bytes, LogData, U256};
18use auto_impl::auto_impl;
19use revm::{
20    context::CreateScheme,
21    interpreter::{
22        CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, InstructionResult,
23    },
24};
25use serde::{Deserialize, Serialize};
26use std::ops::{Deref, DerefMut};
27use tracing::error;
28
29/// Type of call/creation operation
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub enum CallType {
32    /// Regular call to existing contract
33    Call(CallScheme),
34    /// Contract creation via CREATE opcode
35    Create(CreateScheme),
36}
37
38/// Trait for converting inputs to call type representation for trace analysis
39#[auto_impl(&, &mut, Box, Rc, Arc)]
40trait IntoCallType {
41    /// Convert this input type to its corresponding CallType variant
42    fn convert_to_call_type(&self) -> CallType;
43}
44
45impl IntoCallType for CallInputs {
46    fn convert_to_call_type(&self) -> CallType {
47        CallType::Call(self.scheme)
48    }
49}
50
51impl IntoCallType for CreateInputs {
52    fn convert_to_call_type(&self) -> CallType {
53        CallType::Create(self.scheme)
54    }
55}
56
57impl<T> From<T> for CallType
58where
59    T: IntoCallType,
60{
61    fn from(value: T) -> Self {
62        value.convert_to_call_type()
63    }
64}
65
66/// Result of a call/creation operation
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub enum CallResult {
69    /// Call succeeded
70    Success {
71        /// Output data from the call
72        output: Bytes,
73        /// Result
74        result: InstructionResult,
75    },
76    /// Call reverted
77    Revert {
78        /// Output data from the call
79        output: Bytes,
80        /// Result
81        result: InstructionResult,
82    },
83    /// Self-destruct
84    Error {
85        /// Output data from the call
86        output: Bytes,
87        /// Result
88        result: InstructionResult,
89    },
90}
91
92impl PartialEq for CallResult {
93    fn eq(&self, other: &Self) -> bool {
94        match (self, other) {
95            (
96                Self::Success { output: out1, result: res1 },
97                Self::Success { output: out2, result: res2 },
98            ) => {
99                if out1.is_empty() && out2.is_empty() {
100                    // When the return data is empty, we allow STOP equals to RETURN
101                    (res1 == res2)
102                        || (matches!(res1, InstructionResult::Stop)
103                            && matches!(res2, InstructionResult::Return))
104                        || (matches!(res2, InstructionResult::Stop)
105                            && matches!(res1, InstructionResult::Return))
106                } else {
107                    out1 == out2 && res1 == res2
108                }
109            }
110            (
111                Self::Revert { output: out1, result: res1 },
112                Self::Revert { output: out2, result: res2 },
113            ) => out1 == out2 && res1 == res2,
114            (
115                Self::Error { output: out1, result: res1 },
116                Self::Error { output: out2, result: res2 },
117            ) => out1 == out2 && res1 == res2,
118            _ => false,
119        }
120    }
121}
122
123impl Eq for CallResult {}
124
125impl CallResult {
126    /// Get the instruction result code from this call result
127    pub fn result(&self) -> InstructionResult {
128        match self {
129            Self::Success { result, .. } => *result,
130            Self::Revert { result, .. } => *result,
131            Self::Error { result, .. } => *result,
132        }
133    }
134
135    /// Get the output bytes from this call result (return data or revert reason)
136    pub fn output(&self) -> &Bytes {
137        match self {
138            Self::Success { output, .. } => output,
139            Self::Revert { output, .. } => output,
140            Self::Error { output, .. } => output,
141        }
142    }
143}
144
145/// Trait for converting outcomes to call result representation for trace analysis
146#[auto_impl(&, &mut, Box, Rc, Arc)]
147trait IntoCallResult {
148    /// Convert this outcome type to its corresponding CallResult variant
149    fn convert_to_call_result(&self) -> CallResult;
150}
151
152impl IntoCallResult for CallOutcome {
153    fn convert_to_call_result(&self) -> CallResult {
154        if self.result.is_ok() {
155            CallResult::Success { output: self.result.output.clone(), result: self.result.result }
156        } else if self.result.is_revert() {
157            CallResult::Revert { output: self.result.output.clone(), result: self.result.result }
158        } else if self.result.is_error() {
159            CallResult::Error { output: self.result.output.clone(), result: self.result.result }
160        } else {
161            error!("Unexpected call outcome, we use CallResult::Error");
162            CallResult::Error { output: self.result.output.clone(), result: self.result.result }
163        }
164    }
165}
166
167impl IntoCallResult for CreateOutcome {
168    fn convert_to_call_result(&self) -> CallResult {
169        if self.result.is_ok() {
170            CallResult::Success { output: self.result.output.clone(), result: self.result.result }
171        } else if self.result.is_revert() {
172            CallResult::Revert { output: self.result.output.clone(), result: self.result.result }
173        } else if self.result.is_error() {
174            CallResult::Error { output: self.result.output.clone(), result: self.result.result }
175        } else {
176            error!("Unexpected create outcome, we use CallResult::Error");
177            CallResult::Error { output: self.result.output.clone(), result: self.result.result }
178        }
179    }
180}
181
182impl<T> From<T> for CallResult
183where
184    T: IntoCallResult,
185{
186    fn from(value: T) -> Self {
187        value.convert_to_call_result()
188    }
189}
190
191/// Complete execution trace containing all call/creation entries for transaction analysis and debugging
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct Trace {
194    /// Internal vector storing all trace entries in chronological order
195    inner: Vec<TraceEntry>,
196}
197
198impl Deref for Trace {
199    type Target = Vec<TraceEntry>;
200
201    fn deref(&self) -> &Self::Target {
202        &self.inner
203    }
204}
205
206impl DerefMut for Trace {
207    fn deref_mut(&mut self) -> &mut Self::Target {
208        &mut self.inner
209    }
210}
211
212// Convenient explicit iterator methods (optional but nice)
213impl Trace {
214    /// Convert trace to serde_json::Value for RPC serialization
215    pub fn to_json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
216        serde_json::to_value(self)
217    }
218
219    /// Create a new empty trace
220    pub fn new() -> Self {
221        Self::default()
222    }
223
224    /// Add a trace entry to this trace
225    pub fn push(&mut self, entry: TraceEntry) {
226        self.inner.push(entry);
227    }
228
229    /// Get the number of trace entries
230    pub fn len(&self) -> usize {
231        self.inner.len()
232    }
233
234    /// Check if the trace is empty
235    pub fn is_empty(&self) -> bool {
236        self.inner.is_empty()
237    }
238}
239
240// IntoIterator for owned Trace (moves out its contents)
241impl IntoIterator for Trace {
242    type Item = TraceEntry;
243    type IntoIter = std::vec::IntoIter<TraceEntry>;
244    fn into_iter(self) -> Self::IntoIter {
245        self.inner.into_iter()
246    }
247}
248
249// IntoIterator for &Trace (shared iteration)
250impl<'a> IntoIterator for &'a Trace {
251    type Item = &'a TraceEntry;
252    type IntoIter = std::slice::Iter<'a, TraceEntry>;
253    fn into_iter(self) -> Self::IntoIter {
254        self.inner.iter()
255    }
256}
257
258// IntoIterator for &mut Trace (mutable iteration)
259impl<'a> IntoIterator for &'a mut Trace {
260    type Item = &'a mut TraceEntry;
261    type IntoIter = std::slice::IterMut<'a, TraceEntry>;
262    fn into_iter(self) -> Self::IntoIter {
263        self.inner.iter_mut()
264    }
265}
266
267/// Single trace entry representing a call or creation
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct TraceEntry {
270    /// Unique ID of this trace entry (its index in the trace vector)
271    pub id: usize,
272    /// ID of the parent trace entry (None for top-level calls)
273    pub parent_id: Option<usize>,
274    /// Depth in the call stack (0 = top level)
275    pub depth: usize,
276    /// Type of operation
277    pub call_type: CallType,
278    /// Address making the call
279    pub caller: Address,
280    /// Target address for calls, or computed address for creates
281    pub target: Address,
282    /// Address where the code actually lives (for delegate calls)
283    pub code_address: Address,
284    /// Input data / constructor args
285    pub input: Bytes,
286    /// Value transferred
287    pub value: U256,
288    /// Result of the call (populated on call_end)
289    pub result: Option<CallResult>,
290    /// Whether this created a new contract
291    pub created_contract: bool,
292    /// Create scheme for contract creation
293    pub create_scheme: Option<CreateScheme>,
294    /// The underlying running bytecode
295    pub bytecode: Option<Bytes>,
296    /// Label of the target contract
297    pub target_label: Option<String>,
298    /// Self-destruct information
299    pub self_destruct: Option<(Address, U256)>,
300    /// Events
301    pub events: Vec<LogData>,
302    /// The first snapshot id that belongs to this entry
303    pub first_snapshot_id: Option<usize>,
304}
305
306// Pretty print for Trace
307impl Trace {
308    /// Print the trace tree structure showing parent-child relationships with fancy formatting
309    pub fn print_trace_tree(&self) {
310        println!();
311        println!(
312            "\x1b[36m╔══════════════════════════════════════════════════════════════════╗\x1b[0m"
313        );
314        println!(
315            "\x1b[36m║                      EXECUTION TRACE TREE                        ║\x1b[0m"
316        );
317        println!(
318            "\x1b[36m╚══════════════════════════════════════════════════════════════════╝\x1b[0m"
319        );
320        println!();
321
322        // Find root entries (those without parents)
323        let roots: Vec<&TraceEntry> =
324            self.inner.iter().filter(|entry| entry.parent_id.is_none()).collect();
325
326        if roots.is_empty() {
327            println!("  \x1b[90mNo trace entries found\x1b[0m");
328            return;
329        }
330
331        for (i, root) in roots.iter().enumerate() {
332            let is_last = i == roots.len() - 1;
333            self.print_trace_entry(root, 0, is_last, vec![]);
334        }
335
336        println!();
337        println!(
338            "\x1b[36m══════════════════════════════════════════════════════════════════\x1b[0m"
339        );
340        self.print_summary();
341    }
342
343    /// Helper function to recursively print trace entries with fancy indentation
344    fn print_trace_entry(
345        &self,
346        entry: &TraceEntry,
347        indent_level: usize,
348        is_last: bool,
349        mut prefix: Vec<bool>,
350    ) {
351        // Build the tree structure with proper connectors
352        let mut tree_str = String::new();
353        for (i, &is_empty) in prefix.iter().enumerate() {
354            if i < prefix.len() {
355                tree_str.push_str(if is_empty { "    " } else { "\x1b[90m│\x1b[0m   " });
356            }
357        }
358
359        // Add the branch connector
360        let connector = if indent_level > 0 {
361            if is_last {
362                "\x1b[90m└──\x1b[0m "
363            } else {
364                "\x1b[90m├──\x1b[0m "
365            }
366        } else {
367            ""
368        };
369        tree_str.push_str(connector);
370
371        // Format the call type with colors based on operation type
372        let (_call_color, type_color, call_type_str) = match &entry.call_type {
373            CallType::Call(CallScheme::Call) => ("\x1b[94m", "\x1b[34m", "CALL"),
374            CallType::Call(CallScheme::CallCode) => ("\x1b[94m", "\x1b[34m", "CALLCODE"),
375            CallType::Call(CallScheme::DelegateCall) => ("\x1b[96m", "\x1b[36m", "DELEGATECALL"),
376            CallType::Call(CallScheme::StaticCall) => ("\x1b[95m", "\x1b[35m", "STATICCALL"),
377            CallType::Create(CreateScheme::Create) => ("\x1b[93m", "\x1b[33m", "CREATE"),
378            CallType::Create(CreateScheme::Create2 { .. }) => ("\x1b[93m", "\x1b[33m", "CREATE2"),
379            CallType::Create(CreateScheme::Custom { .. }) => {
380                ("\x1b[93m", "\x1b[33m", "CREATE_CUSTOM")
381            }
382        };
383
384        // Format the result indicator
385        let (result_indicator, result_color) = match &entry.result {
386            Some(CallResult::Success { .. }) => ("✓", "\x1b[32m"),
387            Some(CallResult::Revert { .. }) => ("✗", "\x1b[31m"),
388            Some(CallResult::Error { .. }) => {
389                ("☠", "\x1b[31m") // TODO (change icon)
390            }
391            None => ("", ""),
392        };
393
394        // Format value transfer with better visibility
395        let value_str = if entry.value > U256::ZERO {
396            format!(" \x1b[93m[{} ETH]\x1b[0m", format_ether(entry.value))
397        } else {
398            String::new()
399        };
400
401        // Format addresses with better distinction
402        let caller_str = if entry.caller == Address::ZERO {
403            "\x1b[90m0x0\x1b[0m".to_string()
404        } else {
405            format!("\x1b[37m{}\x1b[0m", format_address_short(entry.caller))
406        };
407
408        let target_str = if entry.target == Address::ZERO {
409            "\x1b[90m0x0\x1b[0m".to_string()
410        } else if entry.created_contract {
411            format!("\x1b[92m{}\x1b[0m", format_address_short(entry.target))
412        } else {
413            format!("\x1b[37m{}\x1b[0m", format_address_short(entry.target))
414        };
415
416        // Different arrow based on call type
417        let arrow = if matches!(entry.call_type, CallType::Create(_)) {
418            "\x1b[93m→\x1b[0m"
419        } else {
420            "\x1b[90m→\x1b[0m"
421        };
422
423        // Print the formatted entry with cleaner layout
424        print!("{tree_str}{type_color}{call_type_str:12}\x1b[0m {caller_str} {arrow} {target_str}");
425
426        // Add result indicator at the end
427        if !result_indicator.is_empty() {
428            print!(" {result_color}{result_indicator} \x1b[0m");
429        }
430
431        // Add value if present
432        if !value_str.is_empty() {
433            print!("{value_str}");
434        }
435
436        // Add self-destruct indicator if present
437        if let Some((beneficiary, value)) = &entry.self_destruct {
438            print!(
439                " \x1b[91m SELFDESTRUCT → {} ({} ETH)\x1b[0m",
440                format_address_short(*beneficiary),
441                format_ether(*value)
442            );
443        }
444
445        println!();
446
447        // Print input data with better formatting if significant
448        if entry.input.len() > 4 {
449            let data_preview = format_data_preview(&entry.input);
450            let padding = "    ".repeat(indent_level + 1);
451            println!("{padding}\x1b[90m└ Calldata: {data_preview}\x1b[0m");
452        }
453
454        // Print events if any
455        if !entry.events.is_empty() {
456            let padding = "    ".repeat(indent_level + 1);
457            for (i, event) in entry.events.iter().enumerate() {
458                let event_str = format_event(event);
459                if i == 0 {
460                    println!("{padding}\x1b[96m└ Events:\x1b[0m");
461                }
462                println!("{padding}    \x1b[96m• {event_str}\x1b[0m");
463            }
464        }
465
466        // Print error details if result is Error
467        if let Some(CallResult::Error { output, .. }) = &entry.result {
468            let padding = "    ".repeat(indent_level + 1);
469            let error_msg = if output.is_empty() {
470                "Execution error (no output)".to_string()
471            } else if output.len() >= 4 {
472                // Try to decode as a revert message
473                format!("Error: {}", format_data_preview(output))
474            } else {
475                format!("Error output: 0x{}", hex::encode(output))
476            };
477            println!("{padding}\x1b[91m└ ⚠️  {error_msg}\x1b[0m");
478        }
479
480        // Get children and recursively print them
481        let children = self.get_children(entry.id);
482
483        // Update prefix for children
484        if indent_level > 0 {
485            prefix.push(is_last);
486        }
487
488        for (i, child) in children.iter().enumerate() {
489            let child_is_last = i == children.len() - 1;
490            self.print_trace_entry(child, indent_level + 1, child_is_last, prefix.clone());
491        }
492    }
493
494    /// Print summary statistics about the trace
495    fn print_summary(&self) {
496        let total = self.inner.len();
497        let successful = self
498            .inner
499            .iter()
500            .filter(|e| matches!(e.result, Some(CallResult::Success { .. })))
501            .count();
502        let reverted = self
503            .inner
504            .iter()
505            .filter(|e| matches!(e.result, Some(CallResult::Revert { .. })))
506            .count();
507        let errors = self
508            .inner
509            .iter()
510            .filter(|e| matches!(e.result, Some(CallResult::Error { .. })))
511            .count();
512        let self_destructs = self.inner.iter().filter(|e| e.self_destruct.is_some()).count();
513        let with_events = self.inner.iter().filter(|e| !e.events.is_empty()).count();
514        let total_events: usize = self.inner.iter().map(|e| e.events.len()).sum();
515        let creates =
516            self.inner.iter().filter(|e| matches!(e.call_type, CallType::Create(_))).count();
517        let calls = self.inner.iter().filter(|e| matches!(e.call_type, CallType::Call(_))).count();
518        let max_depth = self.inner.iter().map(|e| e.depth).max().unwrap_or(0);
519
520        println!("\x1b[36mSummary:\x1b[0m");
521        println!("  Total: {total} | \x1b[32mSuccess: {successful}\x1b[0m | \x1b[31mReverts: {reverted}\x1b[0m | \x1b[91mErrors: {errors}\x1b[0m | \x1b[94mCalls: {calls}\x1b[0m | \x1b[93mCreates: {creates}\x1b[0m | Depth: {max_depth}");
522
523        if self_destructs > 0 {
524            println!("  \x1b[91m💀 Self-destructs: {self_destructs}\x1b[0m");
525        }
526
527        if total_events > 0 {
528            println!("  \x1b[96m📝 Events: {total_events} (in {with_events} calls)\x1b[0m");
529        }
530    }
531
532    /// Get the parent trace entry for a given trace entry ID
533    pub fn get_parent(&self, trace_id: usize) -> Option<&TraceEntry> {
534        self.inner
535            .get(trace_id)
536            .and_then(|entry| entry.parent_id.and_then(|parent_id| self.inner.get(parent_id)))
537    }
538
539    /// Get all children trace entries for a given trace entry ID
540    pub fn get_children(&self, trace_id: usize) -> Vec<&TraceEntry> {
541        self.inner.iter().filter(|entry| entry.parent_id == Some(trace_id)).collect()
542    }
543}
544
545// Helper functions for formatting
546
547/// Format an address to a shortened display format
548fn format_address_short(addr: Address) -> String {
549    if addr == Address::ZERO {
550        "0x0".to_string()
551    } else {
552        addr.to_checksum(None)
553    }
554}
555
556/// Format data/input bytes to a preview format
557fn format_data_preview(data: &Bytes) -> String {
558    if data.is_empty() {
559        "0x".to_string()
560    } else if data.len() <= 4 {
561        format!("0x{}", hex::encode(data))
562    } else {
563        // Show function selector and total length
564        format!("0x{}… [{} bytes]", hex::encode(&data[..4]), data.len())
565    }
566}
567
568/// Format Wei value to ETH
569fn format_ether(value: U256) -> String {
570    // Convert Wei to ETH (1 ETH = 10^18 Wei)
571    let eth_value = value.to_string();
572    if eth_value.len() <= 18 {
573        // Less than 1 ETH - show significant digits only
574        let padded = format!("{eth_value:0>18}");
575        let trimmed = padded.trim_end_matches('0');
576        if trimmed.is_empty() {
577            "0".to_string()
578        } else {
579            format!("0.{}", &trimmed[..trimmed.len().min(6)])
580        }
581    } else {
582        // More than 1 ETH
583        let (whole, decimal) = eth_value.split_at(eth_value.len() - 18);
584        let decimal_trimmed = decimal[..4.min(decimal.len())].trim_end_matches('0');
585        if decimal_trimmed.is_empty() {
586            whole.to_string()
587        } else {
588            format!("{whole}.{decimal_trimmed}")
589        }
590    }
591}
592
593/// Format event/log data for display
594fn format_event(event: &LogData) -> String {
595    if event.topics().is_empty() {
596        // Anonymous event or no topics
597        format!("Anonymous event with {} bytes data", event.data.len())
598    } else {
599        // First topic is usually the event signature hash
600        let sig_hash = &event.topics()[0];
601        let additional_topics = event.topics().len() - 1;
602        let data_len = event.data.len();
603
604        // Format the signature hash (first 8 chars)
605        let sig_preview = format!("0x{}...", hex::encode(&sig_hash.as_slice()[..4]));
606
607        if additional_topics > 0 && data_len > 0 {
608            format!("{sig_preview} ({additional_topics} indexed, {data_len} bytes data)")
609        } else if additional_topics > 0 {
610            format!("{sig_preview} ({additional_topics} indexed params)")
611        } else if data_len > 0 {
612            format!("{sig_preview} ({data_len} bytes data)")
613        } else {
614            sig_preview
615        }
616    }
617}