1use 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub enum CallType {
32 Call(CallScheme),
34 Create(CreateScheme),
36}
37
38#[auto_impl(&, &mut, Box, Rc, Arc)]
40trait IntoCallType {
41 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#[derive(Debug, Clone, Serialize, Deserialize)]
68pub enum CallResult {
69 Success {
71 output: Bytes,
73 result: InstructionResult,
75 },
76 Revert {
78 output: Bytes,
80 result: InstructionResult,
82 },
83 Error {
85 output: Bytes,
87 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 (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 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 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#[auto_impl(&, &mut, Box, Rc, Arc)]
147trait IntoCallResult {
148 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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct Trace {
194 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
212impl Trace {
214 pub fn to_json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
216 serde_json::to_value(self)
217 }
218
219 pub fn new() -> Self {
221 Self::default()
222 }
223
224 pub fn push(&mut self, entry: TraceEntry) {
226 self.inner.push(entry);
227 }
228
229 pub fn len(&self) -> usize {
231 self.inner.len()
232 }
233
234 pub fn is_empty(&self) -> bool {
236 self.inner.is_empty()
237 }
238}
239
240impl 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
249impl<'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
258impl<'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#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct TraceEntry {
270 pub id: usize,
272 pub parent_id: Option<usize>,
274 pub depth: usize,
276 pub call_type: CallType,
278 pub caller: Address,
280 pub target: Address,
282 pub code_address: Address,
284 pub input: Bytes,
286 pub value: U256,
288 pub result: Option<CallResult>,
290 pub created_contract: bool,
292 pub create_scheme: Option<CreateScheme>,
294 pub bytecode: Option<Bytes>,
296 pub target_label: Option<String>,
298 pub self_destruct: Option<(Address, U256)>,
300 pub events: Vec<LogData>,
302 pub first_snapshot_id: Option<usize>,
304}
305
306impl Trace {
308 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 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 fn print_trace_entry(
345 &self,
346 entry: &TraceEntry,
347 indent_level: usize,
348 is_last: bool,
349 mut prefix: Vec<bool>,
350 ) {
351 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 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 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 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") }
391 None => ("", ""),
392 };
393
394 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 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 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!("{tree_str}{type_color}{call_type_str:12}\x1b[0m {caller_str} {arrow} {target_str}");
425
426 if !result_indicator.is_empty() {
428 print!(" {result_color}{result_indicator} \x1b[0m");
429 }
430
431 if !value_str.is_empty() {
433 print!("{value_str}");
434 }
435
436 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 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 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 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 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 let children = self.get_children(entry.id);
482
483 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 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 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 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
545fn 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
556fn 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 format!("0x{}… [{} bytes]", hex::encode(&data[..4]), data.len())
565 }
566}
567
568fn format_ether(value: U256) -> String {
570 let eth_value = value.to_string();
572 if eth_value.len() <= 18 {
573 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 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
593fn format_event(event: &LogData) -> String {
595 if event.topics().is_empty() {
596 format!("Anonymous event with {} bytes data", event.data.len())
598 } else {
599 let sig_hash = &event.topics()[0];
601 let additional_topics = event.topics().len() - 1;
602 let data_len = event.data.len();
603
604 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}