1mod sources;
2use crate::CallTraceNode;
3use alloy_dyn_abi::{
4 DynSolType, DynSolValue, Specifier,
5 parser::{Parameters, Storage},
6};
7use alloy_primitives::U256;
8use foundry_common::fmt::format_token;
9use foundry_compilers::artifacts::sourcemap::{Jump, SourceElement};
10use revm::bytecode::opcode::OpCode;
11use revm_inspectors::tracing::types::{CallTraceStep, DecodedInternalCall, DecodedTraceStep};
12pub use sources::{ArtifactData, ContractSources, DebugSourceScope, DebugVariable, SourceData};
13
14#[derive(Clone, Debug)]
15pub struct DebugTraceIdentifier {
16 contracts_sources: ContractSources,
18}
19
20impl DebugTraceIdentifier {
21 pub const 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 Self::identify_node_steps_with_sources(node, &self.contracts_sources, contract_name);
30 }
31
32 pub fn identify_node_steps_with_sources(
34 node: &mut CallTraceNode,
35 sources: &ContractSources,
36 contract_name: &str,
37 ) {
38 DebugStepsWalker::new(node, sources, contract_name).walk();
39 }
40}
41
42struct DebugStepsWalker<'a> {
69 node: &'a mut CallTraceNode,
70 current_step: usize,
71 stack: Vec<(String, usize)>,
72 sources: &'a ContractSources,
73 contract_name: &'a str,
74}
75
76impl<'a> DebugStepsWalker<'a> {
77 pub const fn new(
78 node: &'a mut CallTraceNode,
79 sources: &'a ContractSources,
80 contract_name: &'a str,
81 ) -> Self {
82 Self { node, current_step: 0, stack: Vec::new(), sources, contract_name }
83 }
84
85 fn current_step(&self) -> &CallTraceStep {
86 &self.node.trace.steps[self.current_step]
87 }
88
89 fn src_map(&self, step: usize) -> Option<(SourceElement, &SourceData)> {
90 self.sources.find_source_mapping(
91 self.contract_name,
92 self.node.trace.steps[step].pc as u32,
93 self.node.trace.kind.is_any_create(),
94 )
95 }
96
97 fn prev_src_map(&self) -> Option<(SourceElement, &SourceData)> {
98 if self.current_step == 0 {
99 return None;
100 }
101
102 self.src_map(self.current_step - 1)
103 }
104
105 fn current_src_map(&self) -> Option<(SourceElement, &SourceData)> {
106 self.src_map(self.current_step)
107 }
108
109 fn is_same_loc(&self, step: usize, other: usize) -> bool {
110 let Some((loc, _)) = self.src_map(step) else {
111 return false;
112 };
113 let Some((other_loc, _)) = self.src_map(other) else {
114 return false;
115 };
116
117 loc.offset() == other_loc.offset()
118 && loc.length() == other_loc.length()
119 && loc.index() == other_loc.index()
120 }
121
122 fn jump_in(&mut self) {
124 if self.is_same_loc(self.current_step, self.current_step - 1) {
128 return;
129 }
130
131 let Some((source_element, source)) = self.current_src_map() else {
132 return;
133 };
134
135 if let Some(name) = parse_function_from_loc(source, &source_element) {
136 self.stack.push((name, self.current_step - 1));
137 }
138 }
139
140 fn jump_out(&mut self) {
142 let Some((i, _)) = self.stack.iter().enumerate().rfind(|(_, (_, step_idx))| {
143 self.is_same_loc(*step_idx, self.current_step)
144 || self.is_same_loc(step_idx + 1, self.current_step - 1)
145 }) else {
146 return;
147 };
148 let (func_name, start_idx) = self.stack.split_off(i).swap_remove(0);
151
152 let (inputs, outputs) = self
154 .src_map(start_idx + 1)
155 .and_then(|(source_element, source)| {
156 let start = source_element.offset() as usize;
157 let (fn_definition, _) =
158 source_span(&source.source, start, source_element.length() as usize)?;
159 let fn_definition = fn_definition.replace('\n', "");
160 let (inputs, outputs) = parse_types(&fn_definition);
161
162 Some((
163 inputs.and_then(|t| {
164 decode_step_parameters(
165 &t,
166 &self.node.trace.steps[start_idx + 1],
167 Some(self.node.trace.data.as_ref()),
168 )
169 }),
170 outputs.and_then(|t| decode_step_parameters(&t, self.current_step(), None)),
171 ))
172 })
173 .unwrap_or_default();
174
175 self.node.trace.steps[start_idx].decoded = Some(Box::new(DecodedTraceStep::InternalCall(
176 DecodedInternalCall { func_name, args: inputs, return_data: outputs },
177 self.current_step,
178 )));
179 }
180
181 fn process(&mut self) {
182 if self.current_step().op != OpCode::JUMP && self.current_step().op != OpCode::JUMPDEST {
184 return;
185 }
186
187 let Some((prev_source_element, _)) = self.prev_src_map() else {
188 return;
189 };
190
191 match prev_source_element.jump() {
192 Jump::In => self.jump_in(),
193 Jump::Out => self.jump_out(),
194 _ => {}
195 };
196 }
197
198 fn step(&mut self) {
199 self.process();
200 self.current_step += 1;
201 }
202
203 pub fn walk(mut self) {
204 while self.current_step < self.node.trace.steps.len() {
205 self.step();
206 }
207 }
208}
209
210fn parse_function_from_loc(source: &SourceData, loc: &SourceElement) -> Option<String> {
216 let start = loc.offset() as usize;
217 let (source_part, end) = source_span(&source.source, start, loc.length() as usize)?;
218
219 if !source_part.starts_with("function") {
220 return None;
221 }
222 let function_name = source_part.split_once("function")?.1.split('(').next()?.trim();
223 let contract_name = source.find_contract_name(start, end)?;
224
225 Some(internal_function_identifier(contract_name, function_name, source_part))
226}
227
228fn internal_function_identifier(
229 contract_name: &str,
230 function_name: &str,
231 source_part: &str,
232) -> String {
233 let signature = canonical_function_signature(function_name, source_part)
234 .unwrap_or_else(|| function_name.to_string());
235 format!("{contract_name}::{signature}")
236}
237
238fn canonical_function_signature(function_name: &str, source_part: &str) -> Option<String> {
239 let source_part = source_part.replace('\n', "");
240 let (inputs, _) = parse_types(&source_part);
241 let inputs = inputs?;
242 let types =
243 inputs.params.iter().map(|param| param.resolve().ok()).collect::<Option<Vec<_>>>()?;
244 Some(function_signature(function_name, &types))
245}
246
247pub fn function_signature(function_name: &str, types: &[DynSolType]) -> String {
249 let mut signature = String::new();
250 signature.push_str(function_name);
251 signature.push('(');
252 for (i, ty) in types.iter().enumerate() {
253 if i > 0 {
254 signature.push(',');
255 }
256 signature.push_str(&ty.sol_type_name());
257 }
258 signature.push(')');
259 signature
260}
261
262fn source_span(source: &str, start: usize, len: usize) -> Option<(&str, usize)> {
263 let end = start.checked_add(len)?;
264
265 Some((source.get(start..end)?, end))
266}
267
268fn parse_types(source: &str) -> (Option<Parameters<'_>>, Option<Parameters<'_>>) {
270 let inputs = source.find('(').and_then(|params_start| {
271 let params_end = params_start + source[params_start..].find(')')?;
272 Parameters::parse(&source[params_start..params_end + 1]).ok()
273 });
274 let outputs = source.find("returns").and_then(|returns_start| {
275 let return_params_start = returns_start + source[returns_start..].find('(')?;
276 let return_params_end = return_params_start + source[return_params_start..].find(')')?;
277 Parameters::parse(&source[return_params_start..return_params_end + 1]).ok()
278 });
279
280 (inputs, outputs)
281}
282
283pub fn decode_step_parameters(
286 args: &Parameters<'_>,
287 step: &CallTraceStep,
288 calldata: Option<&[u8]>,
289) -> Option<Vec<String>> {
290 let params = &args.params;
291
292 if params.is_empty() {
293 return Some(vec![]);
294 }
295
296 let types = params
297 .iter()
298 .map(|p| {
299 p.resolve().ok().map(|t| {
300 let slots = stack_slots(&t, p.storage);
301 (t, p.storage, slots)
302 })
303 })
304 .collect::<Vec<_>>();
305
306 let stack = step.stack.as_ref()?;
307 let stack_slots =
308 types.iter().map(|type_| type_.as_ref().map_or(1, |(_, _, slots)| *slots)).sum::<usize>();
309
310 if stack.len() < stack_slots {
311 return None;
312 }
313
314 let inputs = &stack[stack.len() - stack_slots..];
315 let memory = step.memory.as_ref().map(|memory| memory.as_bytes().as_ref());
316 let mut input_idx = 0;
317 let mut decoded = Vec::with_capacity(types.len());
318
319 for type_and_storage in &types {
320 let Some((type_, storage, slots)) = type_and_storage.as_ref() else {
321 input_idx += 1;
322 decoded.push("<unknown>".to_string());
323 continue;
324 };
325 let input = &inputs[input_idx..input_idx + *slots];
326 input_idx += *slots;
327
328 decoded.push(
329 decode_parameter(type_, *storage, input, memory, calldata)
330 .as_ref()
331 .map(format_token)
332 .unwrap_or_else(|| "<unknown>".to_string()),
333 );
334 }
335
336 Some(decoded)
337}
338
339const fn stack_slots(ty: &DynSolType, storage: Option<Storage>) -> usize {
340 match (ty, storage) {
341 (
342 DynSolType::String | DynSolType::Bytes | DynSolType::Array(_),
343 Some(Storage::Calldata),
344 ) => 2,
345 _ => 1,
346 }
347}
348
349fn decode_parameter(
350 ty: &DynSolType,
351 storage: Option<Storage>,
352 stack_words: &[U256],
353 memory: Option<&[u8]>,
354 calldata: Option<&[u8]>,
355) -> Option<DynSolValue> {
356 let input = stack_words.first()?;
357
358 match (ty, storage) {
359 (DynSolType::Uint(8), Some(Storage::Memory | Storage::Storage | Storage::Calldata)) => None,
364 (_, Some(Storage::Storage)) => None,
365 (_, Some(Storage::Memory)) => decode_from_memory(ty, memory?, input.try_into().ok()?),
366 (_, Some(Storage::Calldata)) => decode_from_calldata(ty, calldata?, stack_words),
367 _ => ty.abi_decode(&input.to_be_bytes::<32>()).ok(),
369 }
370}
371
372fn decode_from_calldata(
373 ty: &DynSolType,
374 calldata: &[u8],
375 stack_words: &[U256],
376) -> Option<DynSolValue> {
377 let offset: usize = stack_words.first()?.try_into().ok()?;
378
379 match ty {
380 DynSolType::String | DynSolType::Bytes => {
382 let length: usize = stack_words.get(1)?.try_into().ok()?;
383 let data = memory_range(calldata, offset, length)?;
384
385 match ty {
386 DynSolType::Bytes => Some(DynSolValue::Bytes(data.to_vec())),
387 DynSolType::String => {
388 Some(DynSolValue::String(String::from_utf8_lossy(data).to_string()))
389 }
390 _ => unreachable!(),
391 }
392 }
393 _ => None,
394 }
395}
396
397fn decode_from_memory(ty: &DynSolType, memory: &[u8], location: usize) -> Option<DynSolValue> {
399 let first_word = memory_range(memory, location, 32)?;
400
401 match ty {
402 DynSolType::String | DynSolType::Bytes => {
404 let length: usize = U256::from_be_slice(first_word).try_into().ok()?;
405 let data = memory_range(memory, location.checked_add(32)?, length)?;
406
407 match ty {
408 DynSolType::Bytes => Some(DynSolValue::Bytes(data.to_vec())),
409 DynSolType::String => {
410 Some(DynSolValue::String(String::from_utf8_lossy(data).to_string()))
411 }
412 _ => unreachable!(),
413 }
414 }
415 DynSolType::Array(inner) | DynSolType::FixedArray(inner, _) => {
418 let (length, start) = match ty {
419 DynSolType::FixedArray(_, length) => (*length, location),
420 DynSolType::Array(_) => {
421 (U256::from_be_slice(first_word).try_into().ok()?, location.checked_add(32)?)
422 }
423 _ => unreachable!(),
424 };
425 memory_range(memory, start, length.checked_mul(32)?)?;
426 let mut decoded = Vec::with_capacity(length);
427
428 for i in 0..length {
429 let offset = start.checked_add(i.checked_mul(32)?)?;
430 let location = match inner.as_ref() {
431 DynSolType::String | DynSolType::Bytes | DynSolType::Array(_) => {
433 U256::from_be_slice(memory_range(memory, offset, 32)?).try_into().ok()?
434 }
435 _ => offset,
436 };
437
438 decoded.push(decode_from_memory(inner, memory, location)?);
439 }
440
441 Some(DynSolValue::Array(decoded))
442 }
443 _ => ty.abi_decode(first_word).ok(),
444 }
445}
446
447fn memory_range(memory: &[u8], start: usize, len: usize) -> Option<&[u8]> {
448 memory.get(start..start.checked_add(len)?)
449}
450
451#[cfg(test)]
452mod tests {
453 use super::{
454 decode_from_memory, decode_step_parameters, internal_function_identifier, source_span,
455 };
456 use alloy_dyn_abi::{DynSolType, parser::Parameters};
457 use alloy_primitives::{Bytes, U256};
458 use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult};
459 use revm_inspectors::tracing::types::CallTraceStep;
460
461 fn trace_step(stack: Vec<U256>) -> CallTraceStep {
462 CallTraceStep {
463 pc: 0,
464 op: OpCode::STOP,
465 stack: Some(stack.into_boxed_slice()),
466 push_stack: None,
467 memory: None,
468 returndata: Bytes::new(),
469 gas_remaining: 0,
470 gas_refund_counter: 0,
471 gas_used: 0,
472 gas_cost: 0,
473 storage_change: None,
474 status: Some(InstructionResult::Stop),
475 immediate_bytes: None,
476 decoded: None,
477 }
478 }
479
480 #[test]
481 fn source_span_returns_none_for_invalid_ranges() {
482 assert_eq!(source_span("abcdef", 2, 3), Some(("cde", 5)));
483 assert_eq!(source_span("abcdef", 7, 1), None);
484 assert_eq!(source_span("abcdef", usize::MAX, 1), None);
485 }
486
487 #[test]
488 fn internal_function_identifier_includes_canonical_signature() {
489 assert_eq!(
490 internal_function_identifier(
491 "DebugMe",
492 "foo",
493 "function foo(uint256 amount, bool ok) internal returns (uint256) {",
494 ),
495 "DebugMe::foo(uint256,bool)"
496 );
497 }
498
499 #[test]
500 fn decode_from_memory_rejects_overflow_location() {
501 assert_eq!(decode_from_memory(&DynSolType::Bytes, &[0; 64], usize::MAX), None);
502 }
503
504 #[test]
505 fn decode_from_memory_rejects_oversized_dynamic_array_length() {
506 let memory = U256::from(1_000_000).to_be_bytes::<32>();
507 let ty = DynSolType::Array(Box::new(DynSolType::Uint(256)));
508
509 assert_eq!(decode_from_memory(&ty, &memory, 0), None);
510 }
511
512 #[test]
513 fn decode_step_parameters_marks_storage_params_unknown() {
514 let params = Parameters::parse("(uint256[] storage values)").unwrap();
515 let step = trace_step(vec![U256::from(5)]);
516
517 assert_eq!(
518 decode_step_parameters(¶ms, &step, None),
519 Some(vec!["<unknown>".to_string()])
520 );
521 }
522
523 #[test]
524 fn decode_step_parameters_aligns_static_arg_before_calldata_bytes() {
525 let params = Parameters::parse("(bytes32 digest, bytes calldata signature)").unwrap();
526 let digest = U256::from(0x1234);
527 let offset = 0x44;
528 let mut calldata = vec![0; offset];
529 calldata.extend_from_slice(&[0x11, 0x22, 0x33]);
530 let step = trace_step(vec![digest, U256::from(offset), U256::from(3)]);
531
532 assert_eq!(
533 decode_step_parameters(¶ms, &step, Some(&calldata)),
534 Some(vec![
535 "0x0000000000000000000000000000000000000000000000000000000000001234".to_string(),
536 "0x112233".to_string(),
537 ])
538 );
539 }
540
541 #[test]
542 fn decode_step_parameters_marks_calldata_bytes_unknown_without_calldata() {
543 let params = Parameters::parse("(bytes calldata signature)").unwrap();
544 let step = trace_step(vec![U256::from(0x44), U256::from(3)]);
545
546 assert_eq!(
547 decode_step_parameters(¶ms, &step, None),
548 Some(vec!["<unknown>".to_string()])
549 );
550 }
551
552 #[test]
553 fn decode_step_parameters_aligns_static_arg_after_unsupported_calldata_array() {
554 let params = Parameters::parse("(uint256[] calldata values, bytes32 digest)").unwrap();
555 let digest = U256::from(0x1234);
556 let step = trace_step(vec![U256::from(0x44), U256::from(2), digest]);
557
558 assert_eq!(
559 decode_step_parameters(¶ms, &step, Some(&[])),
560 Some(vec![
561 "<unknown>".to_string(),
562 "0x0000000000000000000000000000000000000000000000000000000000001234".to_string(),
563 ])
564 );
565 }
566}