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