1use crate::{
2 CallTrace, CallTraceArena, CallTraceNode, DecodedCallData,
3 debug::DebugTraceIdentifier,
4 identifier::{IdentifiedAddress, LocalTraceIdentifier, SignaturesIdentifier, TraceIdentifier},
5};
6use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt};
7use alloy_json_abi::{Error, Event, Function, JsonAbi};
8use alloy_primitives::{
9 Address, B256, LogData, Selector,
10 map::{HashMap, HashSet, hash_map::Entry},
11};
12use foundry_common::{
13 ContractsByArtifact, SELECTOR_LEN, abi::get_indexed_event, fmt::format_token,
14 get_contract_name, selectors::SelectorKind,
15};
16use foundry_evm_core::{
17 abi::{Vm, console},
18 constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS},
19 decode::RevertDecoder,
20 precompiles::{
21 BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1,
22 BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY,
23 MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256,
24 },
25};
26use itertools::Itertools;
27use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace};
28use std::{collections::BTreeMap, sync::OnceLock};
29
30mod precompiles;
31
32#[derive(Default)]
34#[must_use = "builders do nothing unless you call `build` on them"]
35pub struct CallTraceDecoderBuilder {
36 decoder: CallTraceDecoder,
37}
38
39impl CallTraceDecoderBuilder {
40 #[inline]
42 pub fn new() -> Self {
43 Self { decoder: CallTraceDecoder::new().clone() }
44 }
45
46 #[inline]
48 pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
49 self.decoder.labels.extend(labels);
50 self
51 }
52
53 #[inline]
55 pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
56 self.decoder.collect_abi(abi, None);
57 self
58 }
59
60 #[inline]
62 pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self {
63 trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs");
64 for contract in contracts.values() {
65 self.decoder.collect_abi(&contract.abi, None);
66 }
67 self
68 }
69
70 #[inline]
72 pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
73 self.with_known_contracts(identifier.contracts())
74 }
75
76 #[inline]
78 pub fn with_verbosity(mut self, level: u8) -> Self {
79 self.decoder.verbosity = level;
80 self
81 }
82
83 #[inline]
85 pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
86 self.decoder.signature_identifier = Some(identifier);
87 self
88 }
89
90 #[inline]
92 pub fn with_label_disabled(mut self, disable_alias: bool) -> Self {
93 self.decoder.disable_labels = disable_alias;
94 self
95 }
96
97 #[inline]
99 pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
100 self.decoder.debug_identifier = Some(identifier);
101 self
102 }
103
104 #[inline]
106 pub fn build(self) -> CallTraceDecoder {
107 self.decoder
108 }
109}
110
111#[derive(Clone, Debug, Default)]
119pub struct CallTraceDecoder {
120 pub contracts: HashMap<Address, String>,
124 pub labels: HashMap<Address, String>,
126 pub receive_contracts: HashSet<Address>,
128 pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
131 pub non_fallback_contracts: HashMap<Address, HashSet<Selector>>,
134
135 pub functions: HashMap<Selector, Vec<Function>>,
137 pub events: BTreeMap<(B256, usize), Vec<Event>>,
141 pub revert_decoder: RevertDecoder,
143
144 pub signature_identifier: Option<SignaturesIdentifier>,
146 pub verbosity: u8,
148
149 pub debug_identifier: Option<DebugTraceIdentifier>,
151
152 pub disable_labels: bool,
154}
155
156impl CallTraceDecoder {
157 pub fn new() -> &'static Self {
162 static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
165 INIT.get_or_init(Self::init)
166 }
167
168 #[instrument(name = "CallTraceDecoder::init", level = "debug")]
169 fn init() -> Self {
170 Self {
171 contracts: Default::default(),
172 labels: HashMap::from_iter([
173 (CHEATCODE_ADDRESS, "VM".to_string()),
174 (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
175 (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
176 (CALLER, "DefaultSender".to_string()),
177 (EC_RECOVER, "ECRecover".to_string()),
178 (SHA_256, "SHA-256".to_string()),
179 (RIPEMD_160, "RIPEMD-160".to_string()),
180 (IDENTITY, "Identity".to_string()),
181 (MOD_EXP, "ModExp".to_string()),
182 (EC_ADD, "ECAdd".to_string()),
183 (EC_MUL, "ECMul".to_string()),
184 (EC_PAIRING, "ECPairing".to_string()),
185 (BLAKE_2F, "Blake2F".to_string()),
186 (POINT_EVALUATION, "PointEvaluation".to_string()),
187 (BLS12_G1ADD, "BLS12_G1ADD".to_string()),
188 (BLS12_G1MSM, "BLS12_G1MSM".to_string()),
189 (BLS12_G2ADD, "BLS12_G2ADD".to_string()),
190 (BLS12_G2MSM, "BLS12_G2MSM".to_string()),
191 (BLS12_PAIRING_CHECK, "BLS12_PAIRING_CHECK".to_string()),
192 (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()),
193 (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()),
194 (P256_VERIFY, "P256VERIFY".to_string()),
195 ]),
196 receive_contracts: Default::default(),
197 fallback_contracts: Default::default(),
198 non_fallback_contracts: Default::default(),
199
200 functions: console::hh::abi::functions()
201 .into_values()
202 .chain(Vm::abi::functions().into_values())
203 .flatten()
204 .map(|func| (func.selector(), vec![func]))
205 .collect(),
206 events: console::ds::abi::events()
207 .into_values()
208 .flatten()
209 .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
210 .collect(),
211 revert_decoder: Default::default(),
212
213 signature_identifier: None,
214 verbosity: 0,
215
216 debug_identifier: None,
217
218 disable_labels: false,
219 }
220 }
221
222 pub fn clear_addresses(&mut self) {
224 self.contracts.clear();
225
226 let default_labels = &Self::new().labels;
227 if self.labels.len() > default_labels.len() {
228 self.labels.clone_from(default_labels);
229 }
230
231 self.receive_contracts.clear();
232 self.fallback_contracts.clear();
233 }
234
235 pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
239 self.collect_identified_addresses(self.identify_addresses(arena, identifier));
240 }
241
242 pub fn identify_addresses<'a>(
246 &self,
247 arena: &CallTraceArena,
248 identifier: &'a mut impl TraceIdentifier,
249 ) -> Vec<IdentifiedAddress<'a>> {
250 let nodes = arena.nodes().iter().filter(|node| {
251 let address = &node.trace.address;
252 !self.labels.contains_key(address) || !self.contracts.contains_key(address)
253 });
254 identifier.identify_addresses(&nodes.collect::<Vec<_>>())
255 }
256
257 pub fn push_event(&mut self, event: Event) {
259 self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
260 }
261
262 pub fn push_function(&mut self, function: Function) {
264 match self.functions.entry(function.selector()) {
265 Entry::Occupied(entry) => {
266 if entry.get().contains(&function) {
268 return;
269 }
270 trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector");
271 entry.into_mut().push(function);
272 }
273 Entry::Vacant(entry) => {
274 entry.insert(vec![function]);
275 }
276 }
277 }
278
279 fn select_contract_function<'a>(
283 &self,
284 functions: &'a [Function],
285 trace: &CallTrace,
286 ) -> &'a [Function] {
287 if functions.len() > 1 {
291 for (i, func) in functions.iter().enumerate() {
292 if trace.data.len() >= SELECTOR_LEN
293 && func.abi_decode_input(&trace.data[SELECTOR_LEN..]).is_ok()
294 {
295 return &functions[i..i + 1];
296 }
297 }
298 }
299 functions
300 }
301
302 pub fn push_error(&mut self, error: Error) {
304 self.revert_decoder.push_error(error);
305 }
306
307 pub fn without_label(&mut self, disable: bool) {
308 self.disable_labels = disable;
309 }
310
311 fn collect_identified_addresses(&mut self, mut addrs: Vec<IdentifiedAddress<'_>>) {
312 addrs.sort_by_key(|identity| identity.address);
313 addrs.dedup_by_key(|identity| identity.address);
314 if addrs.is_empty() {
315 return;
316 }
317
318 trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
319 for IdentifiedAddress { address, label, contract, abi, artifact_id: _ } in addrs {
320 let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
321
322 if let Some(contract) = contract {
323 self.contracts.entry(address).or_insert(contract);
324 }
325
326 if let Some(label) = label {
327 self.labels.entry(address).or_insert(label);
328 }
329
330 if let Some(abi) = abi {
331 self.collect_abi(&abi, Some(address));
332 }
333 }
334 }
335
336 fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>) {
337 let len = abi.len();
338 if len == 0 {
339 return;
340 }
341 trace!(target: "evm::traces", len, ?address, "collecting ABI");
342 for function in abi.functions() {
343 self.push_function(function.clone());
344 }
345 for event in abi.events() {
346 self.push_event(event.clone());
347 }
348 for error in abi.errors() {
349 self.push_error(error.clone());
350 }
351 if let Some(address) = address {
352 if abi.receive.is_some() {
353 self.receive_contracts.insert(address);
354 }
355
356 if abi.fallback.is_some() {
357 self.fallback_contracts
358 .insert(address, abi.functions().map(|f| f.selector()).collect());
359 } else {
360 self.non_fallback_contracts
361 .insert(address, abi.functions().map(|f| f.selector()).collect());
362 }
363 }
364 }
365
366 pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
370 for node in traces {
371 node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await));
372 for log in &mut node.logs {
373 log.decoded = Some(Box::new(self.decode_event(&log.raw_log).await));
374 }
375
376 if let Some(debug) = self.debug_identifier.as_ref()
377 && let Some(identified) = self.contracts.get(&node.trace.address)
378 {
379 debug.identify_node_steps(node, get_contract_name(identified))
380 }
381 }
382 }
383
384 pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
386 let label =
387 if self.disable_labels { None } else { self.labels.get(&trace.address).cloned() };
388
389 if trace.kind.is_any_create() {
390 return DecodedCallTrace { label, ..Default::default() };
391 }
392
393 if let Some(trace) = precompiles::decode(trace, 1) {
394 return trace;
395 }
396
397 let cdata = &trace.data;
398 if trace.address == DEFAULT_CREATE2_DEPLOYER {
399 return DecodedCallTrace {
400 label,
401 call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
402 return_data: self.default_return_data(trace),
403 };
404 }
405
406 if is_abi_call_data(cdata) {
407 let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
408 let mut functions = Vec::new();
409 let functions = match self.functions.get(&selector) {
410 Some(fs) => fs,
411 None => {
412 if let Some(identifier) = &self.signature_identifier
413 && let Some(function) = identifier.identify_function(selector).await
414 {
415 functions.push(function);
416 }
417 &functions
418 }
419 };
420
421 if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address)
424 && !contract_selectors.contains(&selector)
425 && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address))
426 {
427 let return_data = if !trace.success {
428 let revert_msg = self.revert_decoder.decode(&trace.output, trace.status);
429
430 if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") {
431 Some(format!(
432 "unrecognized function selector {} for contract {}, which has no fallback function.",
433 selector, trace.address
434 ))
435 } else {
436 Some(revert_msg)
437 }
438 } else {
439 None
440 };
441
442 return if let Some(func) = functions.first() {
443 DecodedCallTrace {
444 label,
445 call_data: Some(self.decode_function_input(trace, func)),
446 return_data,
447 }
448 } else {
449 DecodedCallTrace {
450 label,
451 call_data: self.fallback_call_data(trace),
452 return_data,
453 }
454 };
455 }
456
457 let contract_functions = self.select_contract_function(functions, trace);
458 let [func, ..] = contract_functions else {
459 return DecodedCallTrace {
460 label,
461 call_data: self.fallback_call_data(trace),
462 return_data: self.default_return_data(trace),
463 };
464 };
465
466 let mut call_data = self.decode_function_input(trace, func);
469 if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address)
470 && !fallback_functions.contains(&selector)
471 && let Some(cd) = self.fallback_call_data(trace)
472 {
473 call_data.signature = cd.signature;
474 }
475
476 DecodedCallTrace {
477 label,
478 call_data: Some(call_data),
479 return_data: self.decode_function_output(trace, contract_functions),
480 }
481 } else {
482 DecodedCallTrace {
483 label,
484 call_data: self.fallback_call_data(trace),
485 return_data: self.default_return_data(trace),
486 }
487 }
488 }
489
490 fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
492 let mut args = None;
493 if trace.data.len() >= SELECTOR_LEN {
494 if trace.address == CHEATCODE_ADDRESS {
495 if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
497 args = Some(v);
498 }
499 }
500
501 if args.is_none()
502 && let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..])
503 {
504 args = Some(v.iter().map(|value| self.format_value(value)).collect());
505 }
506 }
507
508 DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
509 }
510
511 fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
513 match func.name.as_str() {
514 "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]),
515 "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
516 Some(vec!["<pk>".to_string()])
518 }
519 "broadcast" | "startBroadcast" => {
520 if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" {
523 Some(vec!["<pk>".to_string()])
524 } else {
525 None
526 }
527 }
528 "getNonce" => {
529 if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" {
532 Some(vec!["<pk>".to_string()])
533 } else {
534 None
535 }
536 }
537 "sign" | "signP256" => {
538 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
539
540 if !decoded.is_empty() &&
543 (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
544 {
545 decoded[0] = DynSolValue::String("<pk>".to_string());
546 }
547
548 Some(decoded.iter().map(format_token).collect())
549 }
550 "signDelegation" | "signAndAttachDelegation" => {
551 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
552 decoded[1] = DynSolValue::String("<pk>".to_string());
556 Some(decoded.iter().map(format_token).collect())
557 }
558 "parseJson" |
559 "parseJsonUint" |
560 "parseJsonUintArray" |
561 "parseJsonInt" |
562 "parseJsonIntArray" |
563 "parseJsonString" |
564 "parseJsonStringArray" |
565 "parseJsonAddress" |
566 "parseJsonAddressArray" |
567 "parseJsonBool" |
568 "parseJsonBoolArray" |
569 "parseJsonBytes" |
570 "parseJsonBytesArray" |
571 "parseJsonBytes32" |
572 "parseJsonBytes32Array" |
573 "writeJson" |
574 "keyExists" |
576 "keyExistsJson" |
577 "serializeBool" |
578 "serializeUint" |
579 "serializeUintToHex" |
580 "serializeInt" |
581 "serializeAddress" |
582 "serializeBytes32" |
583 "serializeString" |
584 "serializeBytes" => {
585 if self.verbosity >= 5 {
586 None
587 } else {
588 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
589 let token = if func.name.as_str() == "parseJson" ||
590 func.name.as_str() == "keyExists" ||
592 func.name.as_str() == "keyExistsJson"
593 {
594 "<JSON file>"
595 } else {
596 "<stringified JSON>"
597 };
598 decoded[0] = DynSolValue::String(token.to_string());
599 Some(decoded.iter().map(format_token).collect())
600 }
601 }
602 s if s.contains("Toml") => {
603 if self.verbosity >= 5 {
604 None
605 } else {
606 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
607 let token = if func.name.as_str() == "parseToml" ||
608 func.name.as_str() == "keyExistsToml"
609 {
610 "<TOML file>"
611 } else {
612 "<stringified TOML>"
613 };
614 decoded[0] = DynSolValue::String(token.to_string());
615 Some(decoded.iter().map(format_token).collect())
616 }
617 }
618 "createFork" |
619 "createSelectFork" |
620 "rpc" => {
621 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
622
623 if !decoded.is_empty() && func.inputs[0].ty == "string" {
625 let url_or_alias = decoded[0].as_str().unwrap_or_default();
626
627 if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
628 decoded[0] = DynSolValue::String("<rpc url>".to_string());
629 }
630 } else {
631 return None;
632 }
633
634 Some(decoded.iter().map(format_token).collect())
635 }
636 _ => None,
637 }
638 }
639
640 fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
642 if !trace.success {
643 return self.default_return_data(trace);
644 }
645
646 if trace.address == CHEATCODE_ADDRESS
647 && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
648 {
649 return Some(decoded);
650 }
651
652 if let Some(values) =
653 funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok())
654 {
655 if values.is_empty() {
658 return None;
659 }
660
661 return Some(
662 values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
663 );
664 }
665
666 None
667 }
668
669 fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
671 match func.name.as_str() {
672 s if s.starts_with("env") => Some("<env var value>"),
673 "createWallet" | "deriveKey" => Some("<pk>"),
674 "promptSecret" | "promptSecretUint" => Some("<secret>"),
675 "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
676 "readFile" if self.verbosity < 5 => Some("<file>"),
677 "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
678 _ => None,
679 }
680 .map(Into::into)
681 }
682
683 #[track_caller]
684 fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
685 let cdata = &trace.data;
686 let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
687 "receive()"
688 } else if self.fallback_contracts.contains_key(&trace.address) {
689 "fallback()"
690 } else {
691 return None;
692 }
693 .to_string();
694 let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
695 Some(DecodedCallData { signature, args })
696 }
697
698 fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
700 if trace.status.is_none() || trace.status.is_some_and(|s| s.is_ok()) {
705 return None;
706 }
707 (!trace.success).then(|| self.revert_decoder.decode(&trace.output, trace.status))
708 }
709
710 pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
712 let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
713
714 let mut events = Vec::new();
715 let events = match self.events.get(&(t0, log.topics().len() - 1)) {
716 Some(es) => es,
717 None => {
718 if let Some(identifier) = &self.signature_identifier
719 && let Some(event) = identifier.identify_event(t0).await
720 {
721 events.push(get_indexed_event(event, log));
722 }
723 &events
724 }
725 };
726 for event in events {
727 if let Ok(decoded) = event.decode_log(log) {
728 let params = reconstruct_params(event, &decoded);
729 return DecodedCallLog {
730 name: Some(event.name.clone()),
731 params: Some(
732 params
733 .into_iter()
734 .zip(event.inputs.iter())
735 .map(|(param, input)| {
736 let name = input.name.clone();
738 (name, self.format_value(¶m))
739 })
740 .collect(),
741 ),
742 };
743 }
744 }
745
746 DecodedCallLog { name: None, params: None }
747 }
748
749 pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
751 let Some(identifier) = &self.signature_identifier else { return };
752 let events = nodes
753 .iter()
754 .flat_map(|node| {
755 node.logs
756 .iter()
757 .map(|log| log.raw_log.topics())
758 .filter(|&topics| {
759 if let Some(&first) = topics.first()
760 && self.events.contains_key(&(first, topics.len() - 1))
761 {
762 return false;
763 }
764 true
765 })
766 .filter_map(|topics| topics.first())
767 })
768 .copied();
769 let functions = nodes
770 .iter()
771 .filter(|&n| {
772 if n.trace.address == DEFAULT_CREATE2_DEPLOYER
774 || n.is_precompile()
775 || precompiles::is_known_precompile(n.trace.address, 1)
776 {
777 return false;
778 }
779 if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
781 return false;
782 }
783 true
784 })
785 .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
786 .filter(|selector| !self.functions.contains_key(selector));
787 let selectors = events
788 .map(SelectorKind::Event)
789 .chain(functions.map(SelectorKind::Function))
790 .unique()
791 .collect::<Vec<_>>();
792 let _ = identifier.identify(&selectors).await;
793 }
794
795 fn format_value(&self, value: &DynSolValue) -> String {
797 if let DynSolValue::Address(addr) = value
798 && let Some(label) = self.labels.get(addr)
799 {
800 return format!("{label}: [{addr}]");
801 }
802 format_token(value)
803 }
804}
805
806fn is_abi_call_data(data: &[u8]) -> bool {
810 match data.len().cmp(&SELECTOR_LEN) {
811 std::cmp::Ordering::Less => false,
812 std::cmp::Ordering::Equal => true,
813 std::cmp::Ordering::Greater => is_abi_data(&data[SELECTOR_LEN..]),
814 }
815}
816
817fn is_abi_data(data: &[u8]) -> bool {
821 let rem = data.len() % 32;
822 if rem == 0 || data.is_empty() {
823 return true;
824 }
825 data[data.len() - rem..].iter().all(|byte| *byte == 0)
827}
828
829fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
832 let mut indexed = 0;
833 let mut unindexed = 0;
834 let mut inputs = vec![];
835 for input in &event.inputs {
836 if input.indexed && indexed < decoded.indexed.len() {
840 inputs.push(decoded.indexed[indexed].clone());
841 indexed += 1;
842 } else if unindexed < decoded.body.len() {
843 inputs.push(decoded.body[unindexed].clone());
844 unindexed += 1;
845 }
846 }
847
848 inputs
849}
850
851fn indexed_inputs(event: &Event) -> usize {
852 event.inputs.iter().filter(|param| param.indexed).count()
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858 use alloy_primitives::hex;
859
860 #[test]
861 fn test_selector_collision_resolution() {
862 use alloy_json_abi::Function;
863 use alloy_primitives::Address;
864
865 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
867 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
868
869 assert_eq!(func1.selector(), func2.selector());
871
872 let functions = vec![func1, func2];
873
874 let trace = CallTrace {
876 address: Address::from([0x12; 20]),
877 data: hex!("23b872dd000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000064").to_vec().into(),
878 ..Default::default()
879 };
880
881 let decoder = CallTraceDecoder::new();
882 let result = decoder.select_contract_function(&functions, &trace);
883
884 assert_eq!(result.len(), 1);
886 assert_eq!(result[0].signature(), "transferFrom(address,address,uint256)");
887 }
888
889 #[test]
890 fn test_selector_collision_resolution_second_function() {
891 use alloy_json_abi::Function;
892 use alloy_primitives::Address;
893
894 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
896 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
897
898 let functions = vec![func1, func2];
899
900 let trace = CallTrace {
902 address: Address::from([0x12; 20]),
903 data: hex!("23b872dd0000000000000000000000000000000000000000000000000000000000000064")
904 .to_vec()
905 .into(),
906 ..Default::default()
907 };
908
909 let decoder = CallTraceDecoder::new();
910 let result = decoder.select_contract_function(&functions, &trace);
911
912 assert_eq!(result.len(), 1);
914 assert_eq!(result[0].signature(), "gasprice_bit_ether(int128)");
915 }
916
917 #[test]
918 fn test_should_redact() {
919 let decoder = CallTraceDecoder::new();
920
921 let cheatcode_input_test_cases = vec![
923 ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
925 ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
926 ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
927 ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
928 ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
929 ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
930 ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
931 ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
932 ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
935 ("broadcast()", vec![], None), ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
937 ("startBroadcast()", vec![], None), ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
939 ("getNonce(address)", vec![], None), (
943 "sign(uint256,bytes32)",
944 hex!(
945 "
946 e341eaa4
947 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
948 0000000000000000000000000000000000000000000000000000000000000000
949 "
950 )
951 .to_vec(),
952 Some(vec![
953 "\"<pk>\"".to_string(),
954 "0x0000000000000000000000000000000000000000000000000000000000000000"
955 .to_string(),
956 ]),
957 ),
958 (
959 "signP256(uint256,bytes32)",
960 hex!(
961 "
962 83211b40
963 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
964 0000000000000000000000000000000000000000000000000000000000000000
965 "
966 )
967 .to_vec(),
968 Some(vec![
969 "\"<pk>\"".to_string(),
970 "0x0000000000000000000000000000000000000000000000000000000000000000"
971 .to_string(),
972 ]),
973 ),
974 (
975 "createFork(string)",
977 hex!(
978 "
979 31ba3498
980 0000000000000000000000000000000000000000000000000000000000000020
981 000000000000000000000000000000000000000000000000000000000000002c
982 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
983 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
984 "
985 )
986 .to_vec(),
987 Some(vec!["\"<rpc url>\"".to_string()]),
988 ),
989 (
990 "createFork(string)",
992 hex!(
993 "
994 31ba3498
995 0000000000000000000000000000000000000000000000000000000000000020
996 000000000000000000000000000000000000000000000000000000000000002a
997 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
998 76322f6170695f6b657900000000000000000000000000000000000000000000
999 "
1000 )
1001 .to_vec(),
1002 Some(vec!["\"<rpc url>\"".to_string()]),
1003 ),
1004 (
1005 "createFork(string)",
1007 hex!(
1008 "
1009 31ba3498
1010 0000000000000000000000000000000000000000000000000000000000000020
1011 0000000000000000000000000000000000000000000000000000000000000007
1012 6d61696e6e657400000000000000000000000000000000000000000000000000
1013 "
1014 )
1015 .to_vec(),
1016 Some(vec!["\"mainnet\"".to_string()]),
1017 ),
1018 (
1019 "createFork(string,uint256)",
1021 hex!(
1022 "
1023 6ba3ba2b
1024 0000000000000000000000000000000000000000000000000000000000000040
1025 0000000000000000000000000000000000000000000000000000000000000001
1026 000000000000000000000000000000000000000000000000000000000000002c
1027 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1028 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1029 "
1030 )
1031 .to_vec(),
1032 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1033 ),
1034 (
1035 "createFork(string,uint256)",
1037 hex!(
1038 "
1039 6ba3ba2b
1040 0000000000000000000000000000000000000000000000000000000000000040
1041 0000000000000000000000000000000000000000000000000000000000000001
1042 0000000000000000000000000000000000000000000000000000000000000007
1043 6d61696e6e657400000000000000000000000000000000000000000000000000
1044 "
1045 )
1046 .to_vec(),
1047 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1048 ),
1049 (
1050 "createFork(string,bytes32)",
1052 hex!(
1053 "
1054 7ca29682
1055 0000000000000000000000000000000000000000000000000000000000000040
1056 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1057 000000000000000000000000000000000000000000000000000000000000002c
1058 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1059 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1060 "
1061 )
1062 .to_vec(),
1063 Some(vec![
1064 "\"<rpc url>\"".to_string(),
1065 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1066 .to_string(),
1067 ]),
1068 ),
1069 (
1070 "createFork(string,bytes32)",
1073 hex!(
1074 "
1075 7ca29682
1076 0000000000000000000000000000000000000000000000000000000000000040
1077 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1078 0000000000000000000000000000000000000000000000000000000000000007
1079 6d61696e6e657400000000000000000000000000000000000000000000000000
1080 "
1081 )
1082 .to_vec(),
1083 Some(vec![
1084 "\"mainnet\"".to_string(),
1085 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1086 .to_string(),
1087 ]),
1088 ),
1089 (
1090 "createSelectFork(string)",
1092 hex!(
1093 "
1094 98680034
1095 0000000000000000000000000000000000000000000000000000000000000020
1096 000000000000000000000000000000000000000000000000000000000000002c
1097 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1098 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1099 "
1100 )
1101 .to_vec(),
1102 Some(vec!["\"<rpc url>\"".to_string()]),
1103 ),
1104 (
1105 "createSelectFork(string)",
1107 hex!(
1108 "
1109 98680034
1110 0000000000000000000000000000000000000000000000000000000000000020
1111 0000000000000000000000000000000000000000000000000000000000000007
1112 6d61696e6e657400000000000000000000000000000000000000000000000000
1113 "
1114 )
1115 .to_vec(),
1116 Some(vec!["\"mainnet\"".to_string()]),
1117 ),
1118 (
1119 "createSelectFork(string,uint256)",
1121 hex!(
1122 "
1123 71ee464d
1124 0000000000000000000000000000000000000000000000000000000000000040
1125 0000000000000000000000000000000000000000000000000000000000000001
1126 000000000000000000000000000000000000000000000000000000000000002c
1127 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1128 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1129 "
1130 )
1131 .to_vec(),
1132 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1133 ),
1134 (
1135 "createSelectFork(string,uint256)",
1137 hex!(
1138 "
1139 71ee464d
1140 0000000000000000000000000000000000000000000000000000000000000040
1141 0000000000000000000000000000000000000000000000000000000000000001
1142 0000000000000000000000000000000000000000000000000000000000000007
1143 6d61696e6e657400000000000000000000000000000000000000000000000000
1144 "
1145 )
1146 .to_vec(),
1147 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1148 ),
1149 (
1150 "createSelectFork(string,bytes32)",
1152 hex!(
1153 "
1154 84d52b7a
1155 0000000000000000000000000000000000000000000000000000000000000040
1156 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1157 000000000000000000000000000000000000000000000000000000000000002c
1158 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1159 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1160 "
1161 )
1162 .to_vec(),
1163 Some(vec![
1164 "\"<rpc url>\"".to_string(),
1165 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1166 .to_string(),
1167 ]),
1168 ),
1169 (
1170 "createSelectFork(string,bytes32)",
1173 hex!(
1174 "
1175 84d52b7a
1176 0000000000000000000000000000000000000000000000000000000000000040
1177 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1178 0000000000000000000000000000000000000000000000000000000000000007
1179 6d61696e6e657400000000000000000000000000000000000000000000000000
1180 "
1181 )
1182 .to_vec(),
1183 Some(vec![
1184 "\"mainnet\"".to_string(),
1185 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1186 .to_string(),
1187 ]),
1188 ),
1189 (
1190 "rpc(string,string,string)",
1192 hex!(
1193 "
1194 0199a220
1195 0000000000000000000000000000000000000000000000000000000000000060
1196 00000000000000000000000000000000000000000000000000000000000000c0
1197 0000000000000000000000000000000000000000000000000000000000000100
1198 000000000000000000000000000000000000000000000000000000000000002c
1199 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1200 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1201 000000000000000000000000000000000000000000000000000000000000000e
1202 6574685f67657442616c616e6365000000000000000000000000000000000000
1203 0000000000000000000000000000000000000000000000000000000000000034
1204 5b22307835353165373738343737386566386530343865343935646634396632
1205 363134663834613466316463222c22307830225d000000000000000000000000
1206 "
1207 )
1208 .to_vec(),
1209 Some(vec![
1210 "\"<rpc url>\"".to_string(),
1211 "\"eth_getBalance\"".to_string(),
1212 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1213 .to_string(),
1214 ]),
1215 ),
1216 (
1217 "rpc(string,string,string)",
1220 hex!(
1221 "
1222 0199a220
1223 0000000000000000000000000000000000000000000000000000000000000060
1224 00000000000000000000000000000000000000000000000000000000000000a0
1225 00000000000000000000000000000000000000000000000000000000000000e0
1226 0000000000000000000000000000000000000000000000000000000000000007
1227 6d61696e6e657400000000000000000000000000000000000000000000000000
1228 000000000000000000000000000000000000000000000000000000000000000e
1229 6574685f67657442616c616e6365000000000000000000000000000000000000
1230 0000000000000000000000000000000000000000000000000000000000000034
1231 5b22307835353165373738343737386566386530343865343935646634396632
1232 363134663834613466316463222c22307830225d000000000000000000000000
1233 "
1234 )
1235 .to_vec(),
1236 Some(vec![
1237 "\"mainnet\"".to_string(),
1238 "\"eth_getBalance\"".to_string(),
1239 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1240 .to_string(),
1241 ]),
1242 ),
1243 ];
1244
1245 let cheatcode_output_test_cases = vec![
1247 ("createWallet(string)", Some("<pk>".to_string())),
1249 ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1250 ("rpcUrl(string)", Some("<rpc url>".to_string())),
1252 ("rpcUrls()", Some("<rpc url>".to_string())),
1253 ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1254 ];
1255
1256 for (function_signature, data, expected) in cheatcode_input_test_cases {
1257 let function = Function::parse(function_signature).unwrap();
1258 let result = decoder.decode_cheatcode_inputs(&function, &data);
1259 assert_eq!(result, expected, "Input case failed for: {function_signature}");
1260 }
1261
1262 for (function_signature, expected) in cheatcode_output_test_cases {
1263 let function = Function::parse(function_signature).unwrap();
1264 let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1265 assert_eq!(result, expected, "Output case failed for: {function_signature}");
1266 }
1267 }
1268}