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 self.non_fallback_contracts.clear();
234 }
235
236 pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
240 self.collect_identified_addresses(self.identify_addresses(arena, identifier));
241 }
242
243 pub fn identify_addresses<'a>(
247 &self,
248 arena: &CallTraceArena,
249 identifier: &'a mut impl TraceIdentifier,
250 ) -> Vec<IdentifiedAddress<'a>> {
251 let nodes = arena.nodes().iter().filter(|node| {
252 let address = &node.trace.address;
253 !self.labels.contains_key(address) || !self.contracts.contains_key(address)
254 });
255 identifier.identify_addresses(&nodes.collect::<Vec<_>>())
256 }
257
258 pub fn push_event(&mut self, event: Event) {
260 self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
261 }
262
263 pub fn push_function(&mut self, function: Function) {
265 match self.functions.entry(function.selector()) {
266 Entry::Occupied(entry) => {
267 if entry.get().contains(&function) {
269 return;
270 }
271 trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector");
272 entry.into_mut().push(function);
273 }
274 Entry::Vacant(entry) => {
275 entry.insert(vec![function]);
276 }
277 }
278 }
279
280 fn select_contract_function<'a>(
284 &self,
285 functions: &'a [Function],
286 trace: &CallTrace,
287 ) -> &'a [Function] {
288 if functions.len() > 1 {
292 for (i, func) in functions.iter().enumerate() {
293 if trace.data.len() >= SELECTOR_LEN
294 && func.abi_decode_input(&trace.data[SELECTOR_LEN..]).is_ok()
295 {
296 return &functions[i..i + 1];
297 }
298 }
299 }
300 functions
301 }
302
303 pub fn push_error(&mut self, error: Error) {
305 self.revert_decoder.push_error(error);
306 }
307
308 pub fn without_label(&mut self, disable: bool) {
309 self.disable_labels = disable;
310 }
311
312 fn collect_identified_addresses(&mut self, mut addrs: Vec<IdentifiedAddress<'_>>) {
313 addrs.sort_by_key(|identity| identity.address);
314 addrs.dedup_by_key(|identity| identity.address);
315 if addrs.is_empty() {
316 return;
317 }
318
319 trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
320 for IdentifiedAddress { address, label, contract, abi, artifact_id: _ } in addrs {
321 let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
322
323 if let Some(contract) = contract {
324 self.contracts.entry(address).or_insert(contract);
325 }
326
327 if let Some(label) = label.filter(|s| !s.is_empty()) {
328 self.labels.entry(address).or_insert(label);
329 }
330
331 if let Some(abi) = abi {
332 self.collect_abi(&abi, Some(address));
333 }
334 }
335 }
336
337 fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>) {
338 let len = abi.len();
339 if len == 0 {
340 return;
341 }
342 trace!(target: "evm::traces", len, ?address, "collecting ABI");
343 for function in abi.functions() {
344 self.push_function(function.clone());
345 }
346 for event in abi.events() {
347 self.push_event(event.clone());
348 }
349 for error in abi.errors() {
350 self.push_error(error.clone());
351 }
352 if let Some(address) = address {
353 if abi.receive.is_some() {
354 self.receive_contracts.insert(address);
355 }
356
357 if abi.fallback.is_some() {
358 self.fallback_contracts
359 .insert(address, abi.functions().map(|f| f.selector()).collect());
360 } else {
361 self.non_fallback_contracts
362 .insert(address, abi.functions().map(|f| f.selector()).collect());
363 }
364 }
365 }
366
367 pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
371 for node in traces {
372 node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await));
373 for log in &mut node.logs {
374 log.decoded = Some(Box::new(self.decode_event(&log.raw_log).await));
375 }
376
377 if let Some(debug) = self.debug_identifier.as_ref()
378 && let Some(identified) = self.contracts.get(&node.trace.address)
379 {
380 debug.identify_node_steps(node, get_contract_name(identified))
381 }
382 }
383 }
384
385 pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
387 let label =
388 if self.disable_labels { None } else { self.labels.get(&trace.address).cloned() };
389
390 if trace.kind.is_any_create() {
391 return DecodedCallTrace { label, ..Default::default() };
392 }
393
394 if let Some(trace) = precompiles::decode(trace, 1) {
395 return trace;
396 }
397
398 let cdata = &trace.data;
399 if trace.address == DEFAULT_CREATE2_DEPLOYER {
400 return DecodedCallTrace {
401 label,
402 call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
403 return_data: self.default_return_data(trace),
404 };
405 }
406
407 if is_abi_call_data(cdata) {
408 let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
409 let mut functions = Vec::new();
410 let functions = match self.functions.get(&selector) {
411 Some(fs) => fs,
412 None => {
413 if let Some(identifier) = &self.signature_identifier
414 && let Some(function) = identifier.identify_function(selector).await
415 {
416 functions.push(function);
417 }
418 &functions
419 }
420 };
421
422 if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address)
425 && !contract_selectors.contains(&selector)
426 && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address))
427 {
428 let return_data = if !trace.success {
429 let revert_msg = self.revert_decoder.decode(&trace.output, trace.status);
430
431 if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") {
432 Some(format!(
433 "unrecognized function selector {} for contract {}, which has no fallback function.",
434 selector, trace.address
435 ))
436 } else {
437 Some(revert_msg)
438 }
439 } else {
440 None
441 };
442
443 return if let Some(func) = functions.first() {
444 DecodedCallTrace {
445 label,
446 call_data: Some(self.decode_function_input(trace, func)),
447 return_data,
448 }
449 } else {
450 DecodedCallTrace {
451 label,
452 call_data: self.fallback_call_data(trace),
453 return_data,
454 }
455 };
456 }
457
458 let contract_functions = self.select_contract_function(functions, trace);
459 let [func, ..] = contract_functions else {
460 return DecodedCallTrace {
461 label,
462 call_data: self.fallback_call_data(trace),
463 return_data: self.default_return_data(trace),
464 };
465 };
466
467 let mut call_data = self.decode_function_input(trace, func);
470 if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address)
471 && !fallback_functions.contains(&selector)
472 && let Some(cd) = self.fallback_call_data(trace)
473 {
474 call_data.signature = cd.signature;
475 }
476
477 DecodedCallTrace {
478 label,
479 call_data: Some(call_data),
480 return_data: self.decode_function_output(trace, contract_functions),
481 }
482 } else {
483 DecodedCallTrace {
484 label,
485 call_data: self.fallback_call_data(trace),
486 return_data: self.default_return_data(trace),
487 }
488 }
489 }
490
491 fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
493 let mut args = None;
494 if trace.data.len() >= SELECTOR_LEN {
495 if trace.address == CHEATCODE_ADDRESS {
496 if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
498 args = Some(v);
499 }
500 }
501
502 if args.is_none()
503 && let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..])
504 {
505 args = Some(v.iter().map(|value| self.format_value(value)).collect());
506 }
507 }
508
509 DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
510 }
511
512 fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
514 match func.name.as_str() {
515 "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]),
516 "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
517 Some(vec!["<pk>".to_string()])
519 }
520 "broadcast" | "startBroadcast" => {
521 if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" {
524 Some(vec!["<pk>".to_string()])
525 } else {
526 None
527 }
528 }
529 "getNonce" => {
530 if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" {
533 Some(vec!["<pk>".to_string()])
534 } else {
535 None
536 }
537 }
538 "sign" | "signP256" => {
539 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
540
541 if !decoded.is_empty() &&
544 (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
545 {
546 decoded[0] = DynSolValue::String("<pk>".to_string());
547 }
548
549 Some(decoded.iter().map(format_token).collect())
550 }
551 "signDelegation" | "signAndAttachDelegation" => {
552 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
553 decoded[1] = DynSolValue::String("<pk>".to_string());
557 Some(decoded.iter().map(format_token).collect())
558 }
559 "parseJson" |
560 "parseJsonUint" |
561 "parseJsonUintArray" |
562 "parseJsonInt" |
563 "parseJsonIntArray" |
564 "parseJsonString" |
565 "parseJsonStringArray" |
566 "parseJsonAddress" |
567 "parseJsonAddressArray" |
568 "parseJsonBool" |
569 "parseJsonBoolArray" |
570 "parseJsonBytes" |
571 "parseJsonBytesArray" |
572 "parseJsonBytes32" |
573 "parseJsonBytes32Array" |
574 "writeJson" |
575 "keyExists" |
577 "keyExistsJson" |
578 "serializeBool" |
579 "serializeUint" |
580 "serializeUintToHex" |
581 "serializeInt" |
582 "serializeAddress" |
583 "serializeBytes32" |
584 "serializeString" |
585 "serializeBytes" => {
586 if self.verbosity >= 5 {
587 None
588 } else {
589 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
590 let token = if func.name.as_str() == "parseJson" ||
591 func.name.as_str() == "keyExists" ||
593 func.name.as_str() == "keyExistsJson"
594 {
595 "<JSON file>"
596 } else {
597 "<stringified JSON>"
598 };
599 decoded[0] = DynSolValue::String(token.to_string());
600 Some(decoded.iter().map(format_token).collect())
601 }
602 }
603 s if s.contains("Toml") => {
604 if self.verbosity >= 5 {
605 None
606 } else {
607 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
608 let token = if func.name.as_str() == "parseToml" ||
609 func.name.as_str() == "keyExistsToml"
610 {
611 "<TOML file>"
612 } else {
613 "<stringified TOML>"
614 };
615 decoded[0] = DynSolValue::String(token.to_string());
616 Some(decoded.iter().map(format_token).collect())
617 }
618 }
619 "createFork" |
620 "createSelectFork" |
621 "rpc" => {
622 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
623
624 if !decoded.is_empty() && func.inputs[0].ty == "string" {
626 let url_or_alias = decoded[0].as_str().unwrap_or_default();
627
628 if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
629 decoded[0] = DynSolValue::String("<rpc url>".to_string());
630 }
631 } else {
632 return None;
633 }
634
635 Some(decoded.iter().map(format_token).collect())
636 }
637 _ => None,
638 }
639 }
640
641 fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
643 if !trace.success {
644 return self.default_return_data(trace);
645 }
646
647 if trace.address == CHEATCODE_ADDRESS
648 && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
649 {
650 return Some(decoded);
651 }
652
653 if let Some(values) =
654 funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok())
655 {
656 if values.is_empty() {
659 return None;
660 }
661
662 return Some(
663 values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
664 );
665 }
666
667 None
668 }
669
670 fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
672 match func.name.as_str() {
673 s if s.starts_with("env") => Some("<env var value>"),
674 "createWallet" | "deriveKey" => Some("<pk>"),
675 "promptSecret" | "promptSecretUint" => Some("<secret>"),
676 "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
677 "readFile" if self.verbosity < 5 => Some("<file>"),
678 "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
679 _ => None,
680 }
681 .map(Into::into)
682 }
683
684 #[track_caller]
685 fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
686 let cdata = &trace.data;
687 let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
688 "receive()"
689 } else if self.fallback_contracts.contains_key(&trace.address) {
690 "fallback()"
691 } else {
692 return None;
693 }
694 .to_string();
695 let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
696 Some(DecodedCallData { signature, args })
697 }
698
699 fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
701 if trace.status.is_none() || trace.status.is_some_and(|s| s.is_ok()) {
706 return None;
707 }
708 (!trace.success).then(|| self.revert_decoder.decode(&trace.output, trace.status))
709 }
710
711 pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
713 let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
714
715 let mut events = Vec::new();
716 let events = match self.events.get(&(t0, log.topics().len() - 1)) {
717 Some(es) => es,
718 None => {
719 if let Some(identifier) = &self.signature_identifier
720 && let Some(event) = identifier.identify_event(t0).await
721 {
722 events.push(get_indexed_event(event, log));
723 }
724 &events
725 }
726 };
727 for event in events {
728 if let Ok(decoded) = event.decode_log(log) {
729 let params = reconstruct_params(event, &decoded);
730 return DecodedCallLog {
731 name: Some(event.name.clone()),
732 params: Some(
733 params
734 .into_iter()
735 .zip(event.inputs.iter())
736 .map(|(param, input)| {
737 let name = input.name.clone();
739 (name, self.format_value(¶m))
740 })
741 .collect(),
742 ),
743 };
744 }
745 }
746
747 DecodedCallLog { name: None, params: None }
748 }
749
750 pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
752 let Some(identifier) = &self.signature_identifier else { return };
753 let events = nodes
754 .iter()
755 .flat_map(|node| {
756 node.logs
757 .iter()
758 .map(|log| log.raw_log.topics())
759 .filter(|&topics| {
760 if let Some(&first) = topics.first()
761 && self.events.contains_key(&(first, topics.len() - 1))
762 {
763 return false;
764 }
765 true
766 })
767 .filter_map(|topics| topics.first())
768 })
769 .copied();
770 let functions = nodes
771 .iter()
772 .filter(|&n| {
773 if n.trace.address == DEFAULT_CREATE2_DEPLOYER
775 || n.is_precompile()
776 || precompiles::is_known_precompile(n.trace.address, 1)
777 {
778 return false;
779 }
780 if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
782 return false;
783 }
784 true
785 })
786 .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
787 .filter(|selector| !self.functions.contains_key(selector));
788 let selectors = events
789 .map(SelectorKind::Event)
790 .chain(functions.map(SelectorKind::Function))
791 .unique()
792 .collect::<Vec<_>>();
793 let _ = identifier.identify(&selectors).await;
794 }
795
796 fn format_value(&self, value: &DynSolValue) -> String {
798 if let DynSolValue::Address(addr) = value
799 && let Some(label) = self.labels.get(addr)
800 {
801 return format!("{label}: [{addr}]");
802 }
803 format_token(value)
804 }
805}
806
807fn is_abi_call_data(data: &[u8]) -> bool {
811 match data.len().cmp(&SELECTOR_LEN) {
812 std::cmp::Ordering::Less => false,
813 std::cmp::Ordering::Equal => true,
814 std::cmp::Ordering::Greater => is_abi_data(&data[SELECTOR_LEN..]),
815 }
816}
817
818fn is_abi_data(data: &[u8]) -> bool {
822 let rem = data.len() % 32;
823 if rem == 0 || data.is_empty() {
824 return true;
825 }
826 data[data.len() - rem..].iter().all(|byte| *byte == 0)
828}
829
830fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
833 let mut indexed = 0;
834 let mut unindexed = 0;
835 let mut inputs = vec![];
836 for input in &event.inputs {
837 if input.indexed && indexed < decoded.indexed.len() {
841 inputs.push(decoded.indexed[indexed].clone());
842 indexed += 1;
843 } else if unindexed < decoded.body.len() {
844 inputs.push(decoded.body[unindexed].clone());
845 unindexed += 1;
846 }
847 }
848
849 inputs
850}
851
852fn indexed_inputs(event: &Event) -> usize {
853 event.inputs.iter().filter(|param| param.indexed).count()
854}
855
856#[cfg(test)]
857mod tests {
858 use super::*;
859 use alloy_primitives::hex;
860
861 #[test]
862 fn test_selector_collision_resolution() {
863 use alloy_json_abi::Function;
864 use alloy_primitives::Address;
865
866 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
868 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
869
870 assert_eq!(func1.selector(), func2.selector());
872
873 let functions = vec![func1, func2];
874
875 let trace = CallTrace {
877 address: Address::from([0x12; 20]),
878 data: hex!("23b872dd000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000064").to_vec().into(),
879 ..Default::default()
880 };
881
882 let decoder = CallTraceDecoder::new();
883 let result = decoder.select_contract_function(&functions, &trace);
884
885 assert_eq!(result.len(), 1);
887 assert_eq!(result[0].signature(), "transferFrom(address,address,uint256)");
888 }
889
890 #[test]
891 fn test_selector_collision_resolution_second_function() {
892 use alloy_json_abi::Function;
893 use alloy_primitives::Address;
894
895 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
897 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
898
899 let functions = vec![func1, func2];
900
901 let trace = CallTrace {
903 address: Address::from([0x12; 20]),
904 data: hex!("23b872dd0000000000000000000000000000000000000000000000000000000000000064")
905 .to_vec()
906 .into(),
907 ..Default::default()
908 };
909
910 let decoder = CallTraceDecoder::new();
911 let result = decoder.select_contract_function(&functions, &trace);
912
913 assert_eq!(result.len(), 1);
915 assert_eq!(result[0].signature(), "gasprice_bit_ether(int128)");
916 }
917
918 #[test]
919 fn test_should_redact() {
920 let decoder = CallTraceDecoder::new();
921
922 let cheatcode_input_test_cases = vec![
924 ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
926 ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
927 ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
928 ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
929 ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
930 ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
931 ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
932 ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
933 ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
936 ("broadcast()", vec![], None), ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
938 ("startBroadcast()", vec![], None), ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
940 ("getNonce(address)", vec![], None), (
944 "sign(uint256,bytes32)",
945 hex!(
946 "
947 e341eaa4
948 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
949 0000000000000000000000000000000000000000000000000000000000000000
950 "
951 )
952 .to_vec(),
953 Some(vec![
954 "\"<pk>\"".to_string(),
955 "0x0000000000000000000000000000000000000000000000000000000000000000"
956 .to_string(),
957 ]),
958 ),
959 (
960 "signP256(uint256,bytes32)",
961 hex!(
962 "
963 83211b40
964 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
965 0000000000000000000000000000000000000000000000000000000000000000
966 "
967 )
968 .to_vec(),
969 Some(vec![
970 "\"<pk>\"".to_string(),
971 "0x0000000000000000000000000000000000000000000000000000000000000000"
972 .to_string(),
973 ]),
974 ),
975 (
976 "createFork(string)",
978 hex!(
979 "
980 31ba3498
981 0000000000000000000000000000000000000000000000000000000000000020
982 000000000000000000000000000000000000000000000000000000000000002c
983 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
984 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
985 "
986 )
987 .to_vec(),
988 Some(vec!["\"<rpc url>\"".to_string()]),
989 ),
990 (
991 "createFork(string)",
993 hex!(
994 "
995 31ba3498
996 0000000000000000000000000000000000000000000000000000000000000020
997 000000000000000000000000000000000000000000000000000000000000002a
998 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
999 76322f6170695f6b657900000000000000000000000000000000000000000000
1000 "
1001 )
1002 .to_vec(),
1003 Some(vec!["\"<rpc url>\"".to_string()]),
1004 ),
1005 (
1006 "createFork(string)",
1008 hex!(
1009 "
1010 31ba3498
1011 0000000000000000000000000000000000000000000000000000000000000020
1012 0000000000000000000000000000000000000000000000000000000000000007
1013 6d61696e6e657400000000000000000000000000000000000000000000000000
1014 "
1015 )
1016 .to_vec(),
1017 Some(vec!["\"mainnet\"".to_string()]),
1018 ),
1019 (
1020 "createFork(string,uint256)",
1022 hex!(
1023 "
1024 6ba3ba2b
1025 0000000000000000000000000000000000000000000000000000000000000040
1026 0000000000000000000000000000000000000000000000000000000000000001
1027 000000000000000000000000000000000000000000000000000000000000002c
1028 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1029 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1030 "
1031 )
1032 .to_vec(),
1033 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1034 ),
1035 (
1036 "createFork(string,uint256)",
1038 hex!(
1039 "
1040 6ba3ba2b
1041 0000000000000000000000000000000000000000000000000000000000000040
1042 0000000000000000000000000000000000000000000000000000000000000001
1043 0000000000000000000000000000000000000000000000000000000000000007
1044 6d61696e6e657400000000000000000000000000000000000000000000000000
1045 "
1046 )
1047 .to_vec(),
1048 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1049 ),
1050 (
1051 "createFork(string,bytes32)",
1053 hex!(
1054 "
1055 7ca29682
1056 0000000000000000000000000000000000000000000000000000000000000040
1057 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1058 000000000000000000000000000000000000000000000000000000000000002c
1059 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1060 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1061 "
1062 )
1063 .to_vec(),
1064 Some(vec![
1065 "\"<rpc url>\"".to_string(),
1066 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1067 .to_string(),
1068 ]),
1069 ),
1070 (
1071 "createFork(string,bytes32)",
1074 hex!(
1075 "
1076 7ca29682
1077 0000000000000000000000000000000000000000000000000000000000000040
1078 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1079 0000000000000000000000000000000000000000000000000000000000000007
1080 6d61696e6e657400000000000000000000000000000000000000000000000000
1081 "
1082 )
1083 .to_vec(),
1084 Some(vec![
1085 "\"mainnet\"".to_string(),
1086 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1087 .to_string(),
1088 ]),
1089 ),
1090 (
1091 "createSelectFork(string)",
1093 hex!(
1094 "
1095 98680034
1096 0000000000000000000000000000000000000000000000000000000000000020
1097 000000000000000000000000000000000000000000000000000000000000002c
1098 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1099 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1100 "
1101 )
1102 .to_vec(),
1103 Some(vec!["\"<rpc url>\"".to_string()]),
1104 ),
1105 (
1106 "createSelectFork(string)",
1108 hex!(
1109 "
1110 98680034
1111 0000000000000000000000000000000000000000000000000000000000000020
1112 0000000000000000000000000000000000000000000000000000000000000007
1113 6d61696e6e657400000000000000000000000000000000000000000000000000
1114 "
1115 )
1116 .to_vec(),
1117 Some(vec!["\"mainnet\"".to_string()]),
1118 ),
1119 (
1120 "createSelectFork(string,uint256)",
1122 hex!(
1123 "
1124 71ee464d
1125 0000000000000000000000000000000000000000000000000000000000000040
1126 0000000000000000000000000000000000000000000000000000000000000001
1127 000000000000000000000000000000000000000000000000000000000000002c
1128 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1129 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1130 "
1131 )
1132 .to_vec(),
1133 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1134 ),
1135 (
1136 "createSelectFork(string,uint256)",
1138 hex!(
1139 "
1140 71ee464d
1141 0000000000000000000000000000000000000000000000000000000000000040
1142 0000000000000000000000000000000000000000000000000000000000000001
1143 0000000000000000000000000000000000000000000000000000000000000007
1144 6d61696e6e657400000000000000000000000000000000000000000000000000
1145 "
1146 )
1147 .to_vec(),
1148 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1149 ),
1150 (
1151 "createSelectFork(string,bytes32)",
1153 hex!(
1154 "
1155 84d52b7a
1156 0000000000000000000000000000000000000000000000000000000000000040
1157 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1158 000000000000000000000000000000000000000000000000000000000000002c
1159 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1160 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1161 "
1162 )
1163 .to_vec(),
1164 Some(vec![
1165 "\"<rpc url>\"".to_string(),
1166 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1167 .to_string(),
1168 ]),
1169 ),
1170 (
1171 "createSelectFork(string,bytes32)",
1174 hex!(
1175 "
1176 84d52b7a
1177 0000000000000000000000000000000000000000000000000000000000000040
1178 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1179 0000000000000000000000000000000000000000000000000000000000000007
1180 6d61696e6e657400000000000000000000000000000000000000000000000000
1181 "
1182 )
1183 .to_vec(),
1184 Some(vec![
1185 "\"mainnet\"".to_string(),
1186 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1187 .to_string(),
1188 ]),
1189 ),
1190 (
1191 "rpc(string,string,string)",
1193 hex!(
1194 "
1195 0199a220
1196 0000000000000000000000000000000000000000000000000000000000000060
1197 00000000000000000000000000000000000000000000000000000000000000c0
1198 0000000000000000000000000000000000000000000000000000000000000100
1199 000000000000000000000000000000000000000000000000000000000000002c
1200 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1201 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1202 000000000000000000000000000000000000000000000000000000000000000e
1203 6574685f67657442616c616e6365000000000000000000000000000000000000
1204 0000000000000000000000000000000000000000000000000000000000000034
1205 5b22307835353165373738343737386566386530343865343935646634396632
1206 363134663834613466316463222c22307830225d000000000000000000000000
1207 "
1208 )
1209 .to_vec(),
1210 Some(vec![
1211 "\"<rpc url>\"".to_string(),
1212 "\"eth_getBalance\"".to_string(),
1213 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1214 .to_string(),
1215 ]),
1216 ),
1217 (
1218 "rpc(string,string,string)",
1221 hex!(
1222 "
1223 0199a220
1224 0000000000000000000000000000000000000000000000000000000000000060
1225 00000000000000000000000000000000000000000000000000000000000000a0
1226 00000000000000000000000000000000000000000000000000000000000000e0
1227 0000000000000000000000000000000000000000000000000000000000000007
1228 6d61696e6e657400000000000000000000000000000000000000000000000000
1229 000000000000000000000000000000000000000000000000000000000000000e
1230 6574685f67657442616c616e6365000000000000000000000000000000000000
1231 0000000000000000000000000000000000000000000000000000000000000034
1232 5b22307835353165373738343737386566386530343865343935646634396632
1233 363134663834613466316463222c22307830225d000000000000000000000000
1234 "
1235 )
1236 .to_vec(),
1237 Some(vec![
1238 "\"mainnet\"".to_string(),
1239 "\"eth_getBalance\"".to_string(),
1240 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1241 .to_string(),
1242 ]),
1243 ),
1244 ];
1245
1246 let cheatcode_output_test_cases = vec![
1248 ("createWallet(string)", Some("<pk>".to_string())),
1250 ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1251 ("rpcUrl(string)", Some("<rpc url>".to_string())),
1253 ("rpcUrls()", Some("<rpc url>".to_string())),
1254 ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1255 ];
1256
1257 for (function_signature, data, expected) in cheatcode_input_test_cases {
1258 let function = Function::parse(function_signature).unwrap();
1259 let result = decoder.decode_cheatcode_inputs(&function, &data);
1260 assert_eq!(result, expected, "Input case failed for: {function_signature}");
1261 }
1262
1263 for (function_signature, expected) in cheatcode_output_test_cases {
1264 let function = Function::parse(function_signature).unwrap();
1265 let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1266 assert_eq!(result, expected, "Output case failed for: {function_signature}");
1267 }
1268 }
1269}