Skip to main content

foundry_evm_traces/debug/
recording.rs

1//! Debug steps recorded straight into a SolDB step table.
2
3use 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/// The step table that the debug recorders of one session append to, in execution order.
14///
15/// Each executor call runs its own clone of the inspector stack. The clones share this sink, so
16/// the steps of the deployment, the setup, and the test go into one table.
17#[derive(Clone)]
18pub struct DebugStepSink(Arc<Mutex<Option<StepTable>>>);
19
20impl DebugStepSink {
21    /// An empty sink.
22    pub fn new() -> Self {
23        Self(Arc::new(Mutex::new(Some(StepTable::new()))))
24    }
25
26    /// Takes the table, or returns `None` if a recorder holds it.
27    pub fn take(&self) -> Option<StepTable> {
28        self.lock().take()
29    }
30
31    /// Gives the table back.
32    pub fn put(&self, table: StepTable) {
33        *self.lock() = Some(table);
34    }
35
36    /// Locks the table. It is `None` while a recorder holds it.
37    pub fn lock(&self) -> MutexGuard<'_, Option<StepTable>> {
38        self.0.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
39    }
40
41    /// Whether `self` and `other` are the same sink.
42    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/// The steps that one inspector stack recorded for its call trace arena.
60#[derive(Clone, Debug)]
61pub struct DebugRecording {
62    /// The table that holds the steps.
63    pub sink: DebugStepSink,
64    /// The steps of the arena in the sink's table.
65    pub steps: Range<usize>,
66    /// The steps of each arena node, by node index. The step indices count from `steps.start`.
67    pub frames: Vec<RecordedFrame>,
68}
69
70/// The steps that one call or creation spans, its subcalls included.
71#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
72pub struct RecordedFrame {
73    /// The journal depth of the call, as the call trace records it.
74    pub depth: usize,
75    /// The first step of the frame.
76    pub entry_step: usize,
77    /// The step after the last step of the frame.
78    pub exit_step: usize,
79}
80
81/// Converts the state of each step into SolDB's form, sharing the buffers of unchanged steps.
82///
83/// The debug recorder and the SolDB frontend both convert steps through this.
84#[derive(Debug, Default)]
85pub struct StepConverter {
86    /// The last return data buffer and its hex encoding.
87    return_data: Option<(Bytes, Arc<str>)>,
88    /// The storage of the steps that touch no slot.
89    no_storage: Arc<BTreeMap<String, String>>,
90}
91
92/// The storage slots that one step touches, in SolDB's form.
93#[derive(Debug)]
94pub struct StepStorage {
95    /// The slot an `SLOAD` or `SSTORE` touches and the value it reads or writes, or no slot.
96    pub storage: Arc<BTreeMap<String, String>>,
97    /// The change an `SSTORE` makes, or an `SLOAD`'s read.
98    pub storage_diff: Option<BTreeMap<String, StorageChange>>,
99    /// The slot a `TLOAD` or `TSTORE` touches and the value it reads or writes.
100    pub transient_storage: Option<BTreeMap<String, String>>,
101}
102
103impl StepConverter {
104    /// The hex encoding of the return data `buffer`.
105    pub fn return_data(&mut self, buffer: &Bytes) -> Arc<str> {
106        match &self.return_data {
107            // Unchanged return data is the same buffer, so comparing the pointers first skips
108            // most full comparisons.
109            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    /// The slots that opcode `op` touches.
124    ///
125    /// `inputs` ends with the top stack words before the step, `loaded` is the value an `SLOAD`
126    /// or `TLOAD` pushed, and `had_value` the value an `SSTORE` overwrote, if known.
127    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
161/// Formats `value` as a JSON-RPC quantity.
162pub fn quantity(value: U256) -> String {
163    format!("{value:#x}")
164}