foundry_cheatcodes/evm/
record_debug_step.rs1use alloy_primitives::{Bytes, U256};
2use foundry_evm_core::buffer::{BufferKind, get_buffer_accesses};
3use foundry_evm_traces::{
4 CallTraceArena, CallTraceNode, CallTraceStep, RecordedMemory, TraceMemberOrder,
5};
6use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult};
7use spec::Vm::DebugStep;
8
9pub(crate) struct CallTraceCtx<'a> {
11 pub node: &'a CallTraceNode,
12 pub step: &'a CallTraceStep,
13}
14
15pub(crate) fn flatten_call_trace<'a>(
18 root: usize,
19 arena: &'a CallTraceArena,
20 node_start_idx: usize,
21) -> Vec<CallTraceCtx<'a>> {
22 let mut steps = Vec::new();
23 let mut record_started = false;
24
25 recursive_flatten_call_trace(root, arena, node_start_idx, &mut record_started, &mut steps);
27 steps
28}
29
30fn recursive_flatten_call_trace<'a>(
35 node_idx: usize,
36 arena: &'a CallTraceArena,
37 node_start_idx: usize,
38 record_started: &mut bool,
39 flatten_steps: &mut Vec<CallTraceCtx<'a>>,
40) {
41 if !*record_started && node_idx >= node_start_idx {
44 *record_started = true;
45 }
46
47 let node = &arena.nodes()[node_idx];
48
49 for order in &node.ordering {
50 match order {
51 TraceMemberOrder::Step(step_idx) if *record_started => {
52 let step = &node.trace.steps[*step_idx];
53 flatten_steps.push(CallTraceCtx { node, step });
54 }
55 TraceMemberOrder::Call(call_idx) => {
56 let child_node_idx = node.children[*call_idx];
57 recursive_flatten_call_trace(
58 child_node_idx,
59 arena,
60 node_start_idx,
61 record_started,
62 flatten_steps,
63 );
64 }
65 _ => {}
66 }
67 }
68}
69
70pub(crate) fn convert_call_trace_ctx_to_debug_step(ctx: &CallTraceCtx) -> DebugStep {
72 let opcode = ctx.step.op.get();
73 let stack = get_stack_inputs_for_opcode(opcode, ctx.step.stack.as_deref());
74
75 let memory =
76 get_memory_input_for_opcode(opcode, ctx.step.stack.as_deref(), ctx.step.memory.as_ref());
77
78 let is_out_of_gas = matches!(
79 ctx.step.status,
80 Some(
81 InstructionResult::OutOfGas
82 | InstructionResult::MemoryOOG
83 | InstructionResult::MemoryLimitOOG
84 | InstructionResult::PrecompileOOG
85 | InstructionResult::InvalidOperandOOG
86 )
87 );
88
89 let depth = ctx.node.trace.depth as u64 + 1;
90 let contract_addr = ctx.node.execution_address();
91
92 DebugStep {
93 stack,
94 memoryInput: memory,
95 opcode: ctx.step.op.get(),
96 depth,
97 isOutOfGas: is_out_of_gas,
98 contractAddr: contract_addr,
99 }
100}
101
102fn get_memory_input_for_opcode(
105 opcode: u8,
106 stack: Option<&[U256]>,
107 memory: Option<&RecordedMemory>,
108) -> Bytes {
109 let mut memory_input = Bytes::new();
110 let Some(stack_data) = stack else { return memory_input };
111 let Some(memory_data) = memory else { return memory_input };
112
113 if let Some(accesses) = get_buffer_accesses(opcode, stack_data)
114 && let Some((BufferKind::Memory, access)) = accesses.read
115 {
116 memory_input = get_slice_from_memory(memory_data.as_bytes(), access.offset, access.len);
117 };
118
119 memory_input
120}
121
122fn get_stack_inputs_for_opcode(opcode: u8, stack: Option<&[U256]>) -> Vec<U256> {
128 let Some(op) = OpCode::new(opcode) else { return Vec::new() };
129 let Some(stack_data) = stack else { return Vec::new() };
130
131 let stack_input_size = (op.inputs() as usize).min(stack_data.len());
132 stack_data.iter().rev().take(stack_input_size).copied().collect()
133}
134
135fn get_slice_from_memory(memory: &Bytes, start_index: usize, size: usize) -> Bytes {
136 let memory_len = memory.len();
137
138 let end_bound = start_index + size;
139
140 if start_index < memory_len && end_bound <= memory_len {
142 return memory.slice(start_index..end_bound);
143 }
144
145 if start_index < memory_len && end_bound > memory_len {
147 let mut result = memory.slice(start_index..memory_len).to_vec();
148 result.resize(size, 0u8);
149 return Bytes::from(result);
150 }
151
152 Bytes::from(vec![0u8; size])
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use revm::bytecode::opcode;
160
161 #[test]
162 fn stack_inputs_are_taken_from_the_top() {
163 let stack = [U256::ONE, U256::from(2), U256::from(3)];
164 assert_eq!(
165 get_stack_inputs_for_opcode(opcode::ADD, Some(&stack)),
166 vec![U256::from(3), U256::from(2)]
167 );
168 }
169
170 #[test]
171 fn stack_inputs_are_truncated_on_underflow() {
172 assert_eq!(get_stack_inputs_for_opcode(opcode::ADD, Some(&[])), Vec::<U256>::new());
173 assert_eq!(
174 get_stack_inputs_for_opcode(opcode::ADD, Some(&[U256::from(7)])),
175 vec![U256::from(7)]
176 );
177 }
178}