1use alloy_primitives::U256;
4use foundry_evm_traces::debug::{DebugRecording, DebugStepSink, RecordedFrame, StepConverter};
5use revm::{
6 Inspector, JournalEntry,
7 bytecode::opcode::OpCode,
8 context::{ContextTr, JournalTr},
9 inspector::JournalExt,
10 interpreter::{
11 CallInputs, CallOutcome, CreateInputs, CreateOutcome, InstructionResult, Interpreter,
12 interpreter_types::{Jumps, LoopControl, ReturnData},
13 },
14};
15use soldb_core::{EvmWord, StepRecord, StepTable};
16use std::{borrow::Borrow, fmt, sync::Arc};
17
18const MAX_INPUTS: usize = 17;
20
21pub struct DebugRecorder {
29 sink: DebugStepSink,
30 table: Option<StepTable>,
32 first_step: usize,
34 frames: Vec<RecordedFrame>,
36 open: Vec<OpenFrame>,
38 pending: Option<PendingStep>,
40 converter: StepConverter,
42}
43
44struct OpenFrame {
46 frame: usize,
48 write: Option<std::ops::Range<usize>>,
50 memory_size: usize,
52}
53
54struct PendingStep {
56 pc: u64,
57 op: OpCode,
58 gas: u64,
59 depth: u64,
60 #[cfg(test)]
61 memory: Vec<u8>,
62 return_data: Arc<str>,
63 height: usize,
65 inputs: [U256; MAX_INPUTS],
67 inputs_len: usize,
68 journal_len: usize,
70}
71
72impl DebugRecorder {
73 pub fn new(sink: DebugStepSink) -> Self {
75 Self {
76 sink,
77 table: None,
78 first_step: 0,
79 frames: vec![RecordedFrame::default()],
81 open: Vec::new(),
82 pending: None,
83 converter: StepConverter::default(),
84 }
85 }
86
87 pub fn finish(&mut self) -> DebugRecording {
89 let end = self.table.as_ref().map_or(self.first_step, StepTable::len);
90 DebugRecording {
91 sink: self.sink.clone(),
92 steps: self.first_step..end,
93 frames: std::mem::take(&mut self.frames),
94 }
95 }
96
97 fn table(&mut self) -> &mut StepTable {
99 if self.table.is_none() {
100 let table = self.sink.take().unwrap_or_else(|| {
103 self.sink = DebugStepSink::new();
104 self.sink.take().unwrap_or_default()
105 });
106 self.first_step = table.len();
107 self.table = Some(table);
108 }
109 self.table.as_mut().unwrap()
110 }
111
112 fn enter(&mut self, depth: usize) {
114 let entry_step = self.table().len() - self.first_step;
115 let frame = RecordedFrame { depth, entry_step, exit_step: entry_step };
116 let index = if depth == 0 {
118 self.frames[0] = frame;
119 0
120 } else {
121 self.frames.push(frame);
122 self.frames.len() - 1
123 };
124 self.table().start_memory_frame();
125 self.open.push(OpenFrame { frame: index, write: None, memory_size: 0 });
126 }
127
128 fn exit(&mut self) {
130 debug_assert!(self.pending.is_none(), "a step did not end before its frame");
131 self.pending = None;
132 let exit_step = self.table().len() - self.first_step;
133 if let Some(open) = self.open.pop() {
134 self.frames[open.frame].exit_step = exit_step;
135 self.table().end_memory_frame();
136 }
137 }
138
139 fn start_step<CTX: ContextTr<Journal: JournalExt>>(
141 &mut self,
142 interp: &Interpreter,
143 context: &CTX,
144 ) {
145 let Some(open) = self.open.last_mut() else { return };
146 let op = OpCode::new_or_unknown(interp.bytecode.opcode());
147
148 let current = interp.memory.borrow();
151 let current = current.context_memory();
152 let table = self.table.as_mut().expect("a frame is open");
153 if let Some(range) = open.write.take() {
154 let start = range.start.min(current.len());
155 let end = range.end.min(current.len());
156 table
157 .write_memory(start as u64, ¤t[start..end], current.len() as u64)
158 .expect("the write is bounded by the interpreter's memory");
159 } else if open.memory_size != current.len() {
160 table
161 .write_memory(0, &[], current.len() as u64)
162 .expect("EVM memory only grows within a frame");
163 }
164 open.memory_size = current.len();
165 open.write = memory_write(op, interp.stack.data());
166 #[cfg(test)]
167 let memory = current.to_vec();
168 drop(current);
169
170 let return_data = self.converter.return_data(interp.return_data.buffer());
171
172 let stack = interp.stack.data();
175 let read = if op.is_valid() { op.inputs() as usize } else { 0 };
176 let inputs_len = read.min(stack.len()).min(MAX_INPUTS);
177 let mut inputs = [U256::ZERO; MAX_INPUTS];
178 inputs[..inputs_len].copy_from_slice(&stack[stack.len() - inputs_len..]);
179
180 let depth = self.frames[open.frame].depth as u64 + 1;
181 self.pending = Some(PendingStep {
182 pc: interp.bytecode.pc() as u64,
183 op,
184 gas: interp.gas.remaining(),
185 depth,
186 #[cfg(test)]
187 memory,
188 return_data,
189 height: stack.len(),
190 inputs,
191 inputs_len,
192 journal_len: context.journal_ref().journal().len(),
193 });
194 }
195
196 fn end_step<CTX: ContextTr<Journal: JournalExt>>(
198 &mut self,
199 interp: &mut Interpreter,
200 context: &CTX,
201 ) {
202 let Some(step) = self.pending.take() else { return };
203 let status =
204 interp.bytecode.action().as_ref().and_then(|action| action.instruction_result());
205 let after = interp.stack.data();
206 let loaded = status.is_none().then(|| after.last().copied()).flatten();
209 let had_value = (step.op == OpCode::SSTORE
210 && context.journal_ref().journal().len() != step.journal_len)
211 .then(|| match context.journal_ref().journal().last() {
212 Some(JournalEntry::StorageChanged { had_value, .. }) => Some(*had_value),
213 _ => None,
214 })
215 .flatten();
216 let gas_cost = step.gas.saturating_sub(interp.gas.remaining());
217 let kept = &after[..(step.height - step.inputs_len).min(after.len())];
218 self.append(step, kept, status, gas_cost, loaded, had_value);
219 }
220
221 pub fn end_step_not_run(&mut self, interp: &Interpreter) {
223 let Some(step) = self.pending.take() else { return };
224 let stack = interp.stack.data();
225 let kept = &stack[..(step.height - step.inputs_len).min(stack.len())];
226 self.append(step, kept, None, 0, None, None);
227 }
228
229 fn append(
234 &mut self,
235 step: PendingStep,
236 kept: &[U256],
237 status: Option<InstructionResult>,
238 gas_cost: u64,
239 loaded: Option<U256>,
240 had_value: Option<U256>,
241 ) {
242 #[cfg(test)]
243 let expected_memory = step.memory;
244 let PendingStep { pc, op, gas, depth, return_data, inputs, inputs_len, .. } = step;
245 let inputs = &inputs[..inputs_len];
246 let storage = self.converter.storage(op.get(), inputs, loaded, had_value);
247 let error = status.filter(|status| !status.is_ok()).map(|status| format!("{status:?}"));
248 let table = self.table.as_mut().expect("a frame is open");
249 table.append(
250 StepRecord {
251 pc,
252 op: op.get(),
253 gas,
254 gas_cost,
255 depth,
256 error,
257 memory: None,
258 storage: Some(storage.storage),
259 storage_diff: storage.storage_diff,
260 transient_storage: storage.transient_storage,
261 return_data: Some(return_data),
262 },
263 kept.iter().chain(inputs).map(|word| EvmWord(word.to_be_bytes())),
264 );
265 #[cfg(test)]
266 assert_eq!(
267 table.step(table.len() - 1).memory().as_deref(),
268 Some(expected_memory.as_slice()),
269 "recorded memory differs at pc {pc}, opcode {op}"
270 );
271 }
272}
273
274impl Default for DebugRecorder {
275 fn default() -> Self {
276 Self::new(DebugStepSink::new())
277 }
278}
279
280impl Clone for DebugRecorder {
282 fn clone(&self) -> Self {
283 Self::new(self.sink.clone())
284 }
285}
286
287impl Drop for DebugRecorder {
289 fn drop(&mut self) {
290 if let Some(table) = self.table.take() {
291 self.sink.put(table);
292 }
293 }
294}
295
296impl fmt::Debug for DebugRecorder {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 f.debug_struct("DebugRecorder")
299 .field("frames", &self.frames.len())
300 .field("steps", &self.table.as_ref().map(StepTable::len))
301 .finish_non_exhaustive()
302 }
303}
304
305impl<CTX: ContextTr<Journal: JournalExt>> Inspector<CTX> for DebugRecorder {
306 #[inline]
307 fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) {
308 self.start_step(interp, context);
309 }
310
311 #[inline]
312 fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) {
313 self.end_step(interp, context);
314 }
315
316 fn call(&mut self, context: &mut CTX, _inputs: &mut CallInputs) -> Option<CallOutcome> {
317 self.enter(context.journal().depth());
318 None
319 }
320
321 fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) {
322 self.exit();
323 }
324
325 fn create(&mut self, context: &mut CTX, _inputs: &mut CreateInputs) -> Option<CreateOutcome> {
326 self.enter(context.journal().depth());
327 None
328 }
329
330 fn create_end(
331 &mut self,
332 _context: &mut CTX,
333 _inputs: &CreateInputs,
334 _outcome: &mut CreateOutcome,
335 ) {
336 self.exit();
337 }
338}
339
340fn memory_write(op: OpCode, stack: &[U256]) -> Option<std::ops::Range<usize>> {
343 let word = |depth: usize| -> Option<usize> {
344 stack.get(stack.len().checked_sub(depth + 1)?)?.try_into().ok()
345 };
346 let (offset, length) = match op {
347 OpCode::MSTORE => (word(0)?, 32),
348 OpCode::MSTORE8 => (word(0)?, 1),
349 OpCode::CALLDATACOPY | OpCode::CODECOPY | OpCode::RETURNDATACOPY | OpCode::MCOPY => {
350 (word(0)?, word(2)?)
351 }
352 OpCode::EXTCODECOPY => (word(1)?, word(3)?),
353 OpCode::CALL | OpCode::CALLCODE => (word(5)?, word(6)?),
354 OpCode::DELEGATECALL | OpCode::STATICCALL => (word(4)?, word(5)?),
355 _ => return None,
356 };
357 Some(offset..offset.saturating_add(length))
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use alloy_primitives::{Address, TxKind, hex};
364 use revm::{
365 Context, InspectEvm, MainBuilder, MainContext,
366 context::TxEnv,
367 database::InMemoryDB,
368 state::{AccountInfo, Bytecode},
369 };
370
371 #[derive(Default)]
372 struct Snapshots(Vec<(Vec<u8>, Vec<EvmWord>)>);
373
374 impl<CTX> Inspector<CTX> for Snapshots {
375 fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
376 self.0.push((
377 interp.memory.borrow().context_memory().to_vec(),
378 interp.stack.data().iter().map(|word| EvmWord(word.to_be_bytes())).collect(),
379 ));
380 }
381 }
382
383 #[test]
384 fn memory_writes_reconstruct_interpreter_state_in_both_directions() {
385 let target = Address::with_last_byte(0x40);
386 let child = Address::with_last_byte(0x20);
387 let mut db = InMemoryDB::default();
388 let parent = hex::decode(concat!(
391 "61abcd5f52", "6089600753", "601f5f60085e", "601f60085f5e", "60295f608037", "60295f61010039", "60295f61018060203c", "6102005150", "604261030052", "60406102e05f5f5f60205af150", "3d5f6104003e", "60206104405f5f60205afa50", "60206104605f5f60205af450", "60206104805f5f5f60205af250", "00"
406 ))
407 .unwrap();
408 db.insert_account_info(
409 target,
410 AccountInfo::from_bytecode(Bytecode::new_raw(parent.into())),
411 );
412 db.insert_account_info(
413 child,
414 AccountInfo::from_bytecode(Bytecode::new_raw(
415 hex::decode("61cafe5f5260205ff3").unwrap().into(),
416 )),
417 );
418 let mut recorder = DebugRecorder::default();
419 let mut snapshots = Snapshots::default();
420 let result = Context::mainnet()
421 .with_db(db)
422 .build_mainnet_with_inspector((&mut recorder, &mut snapshots))
423 .inspect_one_tx(
424 TxEnv::builder()
425 .caller(Address::with_last_byte(0x30))
426 .kind(TxKind::Call(target))
427 .gas_limit(1_000_000)
428 .build()
429 .unwrap(),
430 )
431 .unwrap();
432 assert!(result.is_success());
433 let table = recorder.table.as_ref().unwrap();
434 assert_eq!(table.len(), snapshots.0.len());
435 for index in (0..table.len()).rev().chain(0..table.len()) {
436 let step = table.step(index);
437 assert_eq!(
438 step.memory().as_deref(),
439 Some(snapshots.0[index].0.as_slice()),
440 "memory at {index}"
441 );
442 assert_eq!(step.stack().to_vec(), snapshots.0[index].1, "stack at {index}");
443 }
444 }
445}