foundry_evm_traces/debug/
mod.rs
1mod sources;
2use crate::CallTraceNode;
3use alloy_dyn_abi::{
4 parser::{Parameters, Storage},
5 DynSolType, DynSolValue, Specifier,
6};
7use alloy_primitives::U256;
8use foundry_common::fmt::format_token;
9use foundry_compilers::artifacts::sourcemap::{Jump, SourceElement};
10use revm::interpreter::OpCode;
11use revm_inspectors::tracing::types::{CallTraceStep, DecodedInternalCall, DecodedTraceStep};
12pub use sources::{ArtifactData, ContractSources, SourceData};
13
14#[derive(Clone, Debug)]
15pub struct DebugTraceIdentifier {
16 contracts_sources: ContractSources,
18}
19
20impl DebugTraceIdentifier {
21 pub fn new(contracts_sources: ContractSources) -> Self {
22 Self { contracts_sources }
23 }
24
25 pub fn identify_node_steps(&self, node: &mut CallTraceNode, contract_name: &str) {
29 DebugStepsWalker::new(node, &self.contracts_sources, contract_name).walk();
30 }
31}
32
33struct DebugStepsWalker<'a> {
60 node: &'a mut CallTraceNode,
61 current_step: usize,
62 stack: Vec<(String, usize)>,
63 sources: &'a ContractSources,
64 contract_name: &'a str,
65}
66
67impl<'a> DebugStepsWalker<'a> {
68 pub fn new(
69 node: &'a mut CallTraceNode,
70 sources: &'a ContractSources,
71 contract_name: &'a str,
72 ) -> Self {
73 Self { node, current_step: 0, stack: Vec::new(), sources, contract_name }
74 }
75
76 fn current_step(&self) -> &CallTraceStep {
77 &self.node.trace.steps[self.current_step]
78 }
79
80 fn src_map(&self, step: usize) -> Option<(SourceElement, &SourceData)> {
81 self.sources.find_source_mapping(
82 self.contract_name,
83 self.node.trace.steps[step].pc as u32,
84 self.node.trace.kind.is_any_create(),
85 )
86 }
87
88 fn prev_src_map(&self) -> Option<(SourceElement, &SourceData)> {
89 if self.current_step == 0 {
90 return None;
91 }
92
93 self.src_map(self.current_step - 1)
94 }
95
96 fn current_src_map(&self) -> Option<(SourceElement, &SourceData)> {
97 self.src_map(self.current_step)
98 }
99
100 fn is_same_loc(&self, step: usize, other: usize) -> bool {
101 let Some((loc, _)) = self.src_map(step) else {
102 return false;
103 };
104 let Some((other_loc, _)) = self.src_map(other) else {
105 return false;
106 };
107
108 loc.offset() == other_loc.offset() &&
109 loc.length() == other_loc.length() &&
110 loc.index() == other_loc.index()
111 }
112
113 fn jump_in(&mut self) {
115 if self.is_same_loc(self.current_step, self.current_step - 1) {
119 return;
120 }
121
122 let Some((source_element, source)) = self.current_src_map() else {
123 return;
124 };
125
126 if let Some(name) = parse_function_from_loc(source, &source_element) {
127 self.stack.push((name, self.current_step - 1));
128 }
129 }
130
131 fn jump_out(&mut self) {
133 let Some((i, _)) = self.stack.iter().enumerate().rfind(|(_, (_, step_idx))| {
134 self.is_same_loc(*step_idx, self.current_step) ||
135 self.is_same_loc(step_idx + 1, self.current_step - 1)
136 }) else {
137 return
138 };
139 let (func_name, start_idx) = self.stack.split_off(i).swap_remove(0);
142
143 let (inputs, outputs) = self
145 .src_map(start_idx + 1)
146 .map(|(source_element, source)| {
147 let start = source_element.offset() as usize;
148 let end = start + source_element.length() as usize;
149 let fn_definition = source.source[start..end].replace('\n', "");
150 let (inputs, outputs) = parse_types(&fn_definition);
151
152 (
153 inputs.and_then(|t| {
154 try_decode_args_from_step(&t, &self.node.trace.steps[start_idx + 1])
155 }),
156 outputs.and_then(|t| try_decode_args_from_step(&t, self.current_step())),
157 )
158 })
159 .unwrap_or_default();
160
161 self.node.trace.steps[start_idx].decoded = Some(DecodedTraceStep::InternalCall(
162 DecodedInternalCall { func_name, args: inputs, return_data: outputs },
163 self.current_step,
164 ));
165 }
166
167 fn process(&mut self) {
168 if self.current_step().op != OpCode::JUMP && self.current_step().op != OpCode::JUMPDEST {
170 return;
171 }
172
173 let Some((prev_source_element, _)) = self.prev_src_map() else {
174 return;
175 };
176
177 match prev_source_element.jump() {
178 Jump::In => self.jump_in(),
179 Jump::Out => self.jump_out(),
180 _ => {}
181 };
182 }
183
184 fn step(&mut self) {
185 self.process();
186 self.current_step += 1;
187 }
188
189 pub fn walk(mut self) {
190 while self.current_step < self.node.trace.steps.len() {
191 self.step();
192 }
193 }
194}
195
196fn parse_function_from_loc(source: &SourceData, loc: &SourceElement) -> Option<String> {
201 let start = loc.offset() as usize;
202 let end = start + loc.length() as usize;
203 let source_part = &source.source[start..end];
204 if !source_part.starts_with("function") {
205 return None;
206 }
207 let function_name = source_part.split_once("function")?.1.split('(').next()?.trim();
208 let contract_name = source.find_contract_name(start, end)?;
209
210 Some(format!("{contract_name}::{function_name}"))
211}
212
213fn parse_types(source: &str) -> (Option<Parameters<'_>>, Option<Parameters<'_>>) {
215 let inputs = source.find('(').and_then(|params_start| {
216 let params_end = params_start + source[params_start..].find(')')?;
217 Parameters::parse(&source[params_start..params_end + 1]).ok()
218 });
219 let outputs = source.find("returns").and_then(|returns_start| {
220 let return_params_start = returns_start + source[returns_start..].find('(')?;
221 let return_params_end = return_params_start + source[return_params_start..].find(')')?;
222 Parameters::parse(&source[return_params_start..return_params_end + 1]).ok()
223 });
224
225 (inputs, outputs)
226}
227
228fn try_decode_args_from_step(args: &Parameters<'_>, step: &CallTraceStep) -> Option<Vec<String>> {
230 let params = &args.params;
231
232 if params.is_empty() {
233 return Some(vec![]);
234 }
235
236 let types = params.iter().map(|p| p.resolve().ok().map(|t| (t, p.storage))).collect::<Vec<_>>();
237
238 let stack = step.stack.as_ref()?;
239
240 if stack.len() < types.len() {
241 return None;
242 }
243
244 let inputs = &stack[stack.len() - types.len()..];
245
246 let decoded = inputs
247 .iter()
248 .zip(types.iter())
249 .map(|(input, type_and_storage)| {
250 type_and_storage
251 .as_ref()
252 .and_then(|(type_, storage)| {
253 match (type_, storage) {
254 (DynSolType::Uint(8), Some(Storage::Memory | Storage::Storage)) => None,
259 (_, Some(Storage::Memory)) => decode_from_memory(
260 type_,
261 step.memory.as_ref()?.as_bytes(),
262 input.try_into().ok()?,
263 ),
264 _ => type_.abi_decode(&input.to_be_bytes::<32>()).ok(),
266 }
267 })
268 .as_ref()
269 .map(format_token)
270 .unwrap_or_else(|| "<unknown>".to_string())
271 })
272 .collect();
273
274 Some(decoded)
275}
276
277fn decode_from_memory(ty: &DynSolType, memory: &[u8], location: usize) -> Option<DynSolValue> {
279 let first_word = memory.get(location..location + 32)?;
280
281 match ty {
282 DynSolType::String | DynSolType::Bytes => {
284 let length: usize = U256::from_be_slice(first_word).try_into().ok()?;
285 let data = memory.get(location + 32..location + 32 + length)?;
286
287 match ty {
288 DynSolType::Bytes => Some(DynSolValue::Bytes(data.to_vec())),
289 DynSolType::String => {
290 Some(DynSolValue::String(String::from_utf8_lossy(data).to_string()))
291 }
292 _ => unreachable!(),
293 }
294 }
295 DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => {
298 let (length, start) = match ty {
299 DynSolType::FixedArray(_, length) => (*length, location),
300 DynSolType::Array(_) => {
301 (U256::from_be_slice(first_word).try_into().ok()?, location + 32)
302 }
303 _ => unreachable!(),
304 };
305 let mut decoded = Vec::with_capacity(length);
306
307 for i in 0..length {
308 let offset = start + i * 32;
309 let location = match inner.as_ref() {
310 DynSolType::String | DynSolType::Bytes | DynSolType::Array(_) => {
312 U256::from_be_slice(memory.get(offset..offset + 32)?).try_into().ok()?
313 }
314 _ => offset,
315 };
316
317 decoded.push(decode_from_memory(inner, memory, location)?);
318 }
319
320 Some(DynSolValue::Array(decoded))
321 }
322 _ => ty.abi_decode(first_word).ok(),
323 }
324}