foundry_evm_traces/debug/
recording.rs1use alloy_primitives::{Bytes, U256, hex};
4use revm::bytecode::opcode;
5use soldb_core::{StepTable, StorageChange};
6use std::{
7 collections::BTreeMap,
8 fmt,
9 ops::Range,
10 sync::{Arc, Mutex, MutexGuard},
11};
12
13#[derive(Clone)]
18pub struct DebugStepSink(Arc<Mutex<Option<StepTable>>>);
19
20impl DebugStepSink {
21 pub fn new() -> Self {
23 Self(Arc::new(Mutex::new(Some(StepTable::new()))))
24 }
25
26 pub fn take(&self) -> Option<StepTable> {
28 self.lock().take()
29 }
30
31 pub fn put(&self, table: StepTable) {
33 *self.lock() = Some(table);
34 }
35
36 pub fn lock(&self) -> MutexGuard<'_, Option<StepTable>> {
38 self.0.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
39 }
40
41 pub fn same(&self, other: &Self) -> bool {
43 Arc::ptr_eq(&self.0, &other.0)
44 }
45}
46
47impl Default for DebugStepSink {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl fmt::Debug for DebugStepSink {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.debug_struct("DebugStepSink").finish_non_exhaustive()
56 }
57}
58
59#[derive(Clone, Debug)]
61pub struct DebugRecording {
62 pub sink: DebugStepSink,
64 pub steps: Range<usize>,
66 pub frames: Vec<RecordedFrame>,
68}
69
70#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
72pub struct RecordedFrame {
73 pub depth: usize,
75 pub entry_step: usize,
77 pub exit_step: usize,
79}
80
81#[derive(Debug, Default)]
85pub struct StepConverter {
86 return_data: Option<(Bytes, Arc<str>)>,
88 no_storage: Arc<BTreeMap<String, String>>,
90}
91
92#[derive(Debug)]
94pub struct StepStorage {
95 pub storage: Arc<BTreeMap<String, String>>,
97 pub storage_diff: Option<BTreeMap<String, StorageChange>>,
99 pub transient_storage: Option<BTreeMap<String, String>>,
101}
102
103impl StepConverter {
104 pub fn return_data(&mut self, buffer: &Bytes) -> Arc<str> {
106 match &self.return_data {
107 Some((last, hex))
110 if (last.as_ptr() == buffer.as_ptr() && last.len() == buffer.len())
111 || last == buffer =>
112 {
113 hex.clone()
114 }
115 _ => {
116 let hex = Arc::<str>::from(hex::encode(buffer));
117 self.return_data = Some((buffer.clone(), hex.clone()));
118 hex
119 }
120 }
121 }
122
123 pub fn storage(
128 &self,
129 op: u8,
130 inputs: &[U256],
131 loaded: Option<U256>,
132 had_value: Option<U256>,
133 ) -> StepStorage {
134 let touched = match op {
135 opcode::SLOAD | opcode::TLOAD => inputs.last().copied().zip(loaded),
136 opcode::SSTORE | opcode::TSTORE => match inputs {
137 [.., value, slot] => Some((*slot, *value)),
138 _ => None,
139 },
140 _ => None,
141 };
142 let mut step = StepStorage {
143 storage: self.no_storage.clone(),
144 storage_diff: None,
145 transient_storage: None,
146 };
147 let Some((slot, value)) = touched else { return step };
148 let (slot, value) = (quantity(slot), quantity(value));
149 if matches!(op, opcode::SLOAD | opcode::SSTORE) {
150 let before = had_value.filter(|_| op == opcode::SSTORE).map(quantity);
151 let change = StorageChange { before, after: Some(value.clone()) };
152 step.storage = Arc::new(BTreeMap::from([(slot.clone(), value)]));
153 step.storage_diff = Some(BTreeMap::from([(slot, change)]));
154 } else {
155 step.transient_storage = Some(BTreeMap::from([(slot, value)]));
156 }
157 step
158 }
159}
160
161pub fn quantity(value: U256) -> String {
163 format!("{value:#x}")
164}