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