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