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},
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 foundry_evm_hardforks::TempoHardfork;
27use itertools::Itertools;
28use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace};
29use std::{collections::BTreeMap, sync::OnceLock};
30use tempo_contracts::precompiles::{
31 IAccountKeychain, IAddressRegistry, IFeeManager, IReceivePolicyGuard, ISignatureVerifier,
32 IStablecoinDEX, ITIP20ChannelReserve, ITIP20Factory, ITIP403Registry, IValidatorConfig,
33};
34use tempo_precompiles::{
35 ACCOUNT_KEYCHAIN_ADDRESS, ADDRESS_REGISTRY_ADDRESS, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS,
36 RECEIVE_POLICY_GUARD_ADDRESS, SIGNATURE_VERIFIER_ADDRESS, STABLECOIN_DEX_ADDRESS,
37 TIP_FEE_MANAGER_ADDRESS, TIP20_CHANNEL_RESERVE_ADDRESS, TIP20_FACTORY_ADDRESS,
38 TIP403_REGISTRY_ADDRESS, VALIDATOR_CONFIG_ADDRESS, nonce::INonce, tip20::ITIP20,
39};
40
41mod precompiles;
42
43#[derive(Default)]
45#[must_use = "builders do nothing unless you call `build` on them"]
46pub struct CallTraceDecoderBuilder {
47 decoder: CallTraceDecoder,
48}
49
50impl CallTraceDecoderBuilder {
51 #[inline]
53 pub fn new() -> Self {
54 Self { decoder: CallTraceDecoder::new().clone() }
55 }
56
57 #[inline]
59 pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
60 self.decoder.labels.extend(labels);
61 self
62 }
63
64 #[inline]
66 pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
67 self.decoder.collect_abi(abi, None);
68 self
69 }
70
71 #[inline]
73 pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self {
74 trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs");
75 for contract in contracts.values() {
76 self.decoder.collect_abi(&contract.abi, None);
77 }
78 self
79 }
80
81 #[inline]
83 pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
84 self.with_known_contracts(identifier.contracts())
85 }
86
87 #[inline]
89 pub const fn with_verbosity(mut self, level: u8) -> Self {
90 self.decoder.verbosity = level;
91 self
92 }
93
94 #[inline]
96 pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
97 self.decoder.signature_identifier = Some(identifier);
98 self
99 }
100
101 #[inline]
103 pub const fn with_label_disabled(mut self, disable_alias: bool) -> Self {
104 self.decoder.disable_labels = disable_alias;
105 self
106 }
107
108 #[inline]
110 pub const fn with_chain_id(mut self, chain_id: Option<u64>) -> Self {
111 self.decoder.chain_id = chain_id;
112 self
113 }
114
115 #[inline]
117 pub fn with_tempo_hardfork(mut self, hardfork: Option<TempoHardfork>) -> Self {
118 self.decoder.tempo_hardfork = hardfork;
119 if hardfork.is_some_and(|hardfork| hardfork.is_t5()) {
120 self.decoder
121 .labels
122 .entry(TIP20_CHANNEL_RESERVE_ADDRESS)
123 .or_insert_with(|| "TIP20ChannelReserve".to_string());
124 }
125 if hardfork.is_some_and(|hardfork| hardfork.is_t6()) {
126 self.decoder
127 .labels
128 .entry(RECEIVE_POLICY_GUARD_ADDRESS)
129 .or_insert_with(|| "ReceivePolicyGuard".to_string());
130 }
131 self
132 }
133
134 #[inline]
136 pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
137 self.decoder.debug_identifier = Some(identifier);
138 self
139 }
140
141 #[inline]
143 pub fn build(self) -> CallTraceDecoder {
144 self.decoder
145 }
146}
147
148#[derive(Clone, Debug, Default)]
156pub struct CallTraceDecoder {
157 pub contracts: HashMap<Address, String>,
161 pub labels: HashMap<Address, String>,
163 pub receive_contracts: HashSet<Address>,
165 pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
168 pub non_fallback_contracts: HashMap<Address, HashSet<Selector>>,
171
172 pub functions: HashMap<Selector, Vec<Function>>,
174 pub functions_by_address: HashMap<Address, HashMap<Selector, Vec<Function>>>,
176 pub events: BTreeMap<(B256, usize), Vec<Event>>,
180 pub revert_decoder: RevertDecoder,
182
183 pub signature_identifier: Option<SignaturesIdentifier>,
185 pub verbosity: u8,
187
188 pub debug_identifier: Option<DebugTraceIdentifier>,
190
191 pub disable_labels: bool,
193
194 pub chain_id: Option<u64>,
196
197 pub tempo_hardfork: Option<TempoHardfork>,
199}
200
201impl CallTraceDecoder {
202 pub fn new() -> &'static Self {
207 static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
210 INIT.get_or_init(Self::init)
211 }
212
213 #[instrument(name = "CallTraceDecoder::init", level = "debug")]
214 fn init() -> Self {
215 let tempo_abis = [
217 IFeeManager::abi::contract(),
218 ITIP20::abi::contract(),
219 ITIP403Registry::abi::contract(),
220 ITIP20Factory::abi::contract(),
221 IStablecoinDEX::abi::contract(),
222 INonce::abi::contract(),
223 IValidatorConfig::abi::contract(),
224 IAccountKeychain::abi::contract(),
225 IAddressRegistry::abi::contract(),
226 ITIP20ChannelReserve::abi::contract(),
227 ISignatureVerifier::abi::contract(),
228 IReceivePolicyGuard::abi::contract(),
229 ];
230 Self {
231 contracts: Default::default(),
232 labels: HashMap::from_iter([
233 (CHEATCODE_ADDRESS, "VM".to_string()),
234 (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
235 (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
236 (CALLER, "DefaultSender".to_string()),
237 (EC_RECOVER, "ECRecover".to_string()),
238 (SHA_256, "SHA-256".to_string()),
239 (RIPEMD_160, "RIPEMD-160".to_string()),
240 (IDENTITY, "Identity".to_string()),
241 (MOD_EXP, "ModExp".to_string()),
242 (EC_ADD, "ECAdd".to_string()),
243 (EC_MUL, "ECMul".to_string()),
244 (EC_PAIRING, "ECPairing".to_string()),
245 (BLAKE_2F, "Blake2F".to_string()),
246 (POINT_EVALUATION, "PointEvaluation".to_string()),
247 (BLS12_G1ADD, "BLS12_G1ADD".to_string()),
248 (BLS12_G1MSM, "BLS12_G1MSM".to_string()),
249 (BLS12_G2ADD, "BLS12_G2ADD".to_string()),
250 (BLS12_G2MSM, "BLS12_G2MSM".to_string()),
251 (BLS12_PAIRING_CHECK, "BLS12_PAIRING_CHECK".to_string()),
252 (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()),
253 (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()),
254 (P256_VERIFY, "P256VERIFY".to_string()),
255 (TIP_FEE_MANAGER_ADDRESS, "FeeManager".to_string()),
257 (TIP403_REGISTRY_ADDRESS, "TIP403Registry".to_string()),
258 (TIP20_FACTORY_ADDRESS, "TIP20Factory".to_string()),
259 (STABLECOIN_DEX_ADDRESS, "StablecoinDex".to_string()),
260 (NONCE_PRECOMPILE_ADDRESS, "Nonce".to_string()),
261 (VALIDATOR_CONFIG_ADDRESS, "ValidatorConfig".to_string()),
262 (ACCOUNT_KEYCHAIN_ADDRESS, "AccountKeychain".to_string()),
263 (ADDRESS_REGISTRY_ADDRESS, "AddressRegistry".to_string()),
264 (TIP20_CHANNEL_RESERVE_ADDRESS, "TIP20ChannelReserve".to_string()),
265 (SIGNATURE_VERIFIER_ADDRESS, "SignatureVerifier".to_string()),
266 (RECEIVE_POLICY_GUARD_ADDRESS, "ReceivePolicyGuard".to_string()),
267 (PATH_USD_ADDRESS, "PathUSD".to_string()),
268 ]),
269 receive_contracts: Default::default(),
270 fallback_contracts: Default::default(),
271 non_fallback_contracts: Default::default(),
272
273 functions: console::hh::abi::functions()
274 .into_values()
275 .chain(Vm::abi::functions().into_values())
276 .chain(IFeeManager::abi::functions().into_values())
278 .chain(ITIP20::abi::functions().into_values())
279 .chain(ITIP403Registry::abi::functions().into_values())
280 .chain(ITIP20Factory::abi::functions().into_values())
281 .chain(IStablecoinDEX::abi::functions().into_values())
282 .chain(INonce::abi::functions().into_values())
283 .chain(IValidatorConfig::abi::functions().into_values())
284 .chain(IAccountKeychain::abi::functions().into_values())
285 .chain(IAddressRegistry::abi::functions().into_values())
286 .chain(ITIP20ChannelReserve::abi::functions().into_values())
287 .chain(ISignatureVerifier::abi::functions().into_values())
288 .chain(IReceivePolicyGuard::abi::functions().into_values())
289 .flatten()
290 .map(|func| (func.selector(), vec![func]))
291 .collect(),
292 functions_by_address: Default::default(),
293 events: console::ds::abi::events()
294 .into_values()
295 .chain(IFeeManager::abi::events().into_values())
297 .chain(ITIP20::abi::events().into_values())
298 .chain(ITIP403Registry::abi::events().into_values())
299 .chain(ITIP20Factory::abi::events().into_values())
300 .chain(IStablecoinDEX::abi::events().into_values())
301 .chain(INonce::abi::events().into_values())
302 .chain(IValidatorConfig::abi::events().into_values())
303 .chain(IAccountKeychain::abi::events().into_values())
304 .chain(IAddressRegistry::abi::events().into_values())
305 .chain(ITIP20ChannelReserve::abi::events().into_values())
306 .chain(ISignatureVerifier::abi::events().into_values())
307 .chain(IReceivePolicyGuard::abi::events().into_values())
308 .flatten()
309 .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
310 .collect(),
311 revert_decoder: RevertDecoder::new().with_abis(tempo_abis.iter()),
313
314 signature_identifier: None,
315 verbosity: 0,
316
317 debug_identifier: None,
318
319 disable_labels: false,
320
321 chain_id: None,
322
323 tempo_hardfork: None,
324 }
325 }
326
327 pub fn clear_addresses(&mut self) {
329 self.contracts.clear();
330
331 let default_labels = &Self::new().labels;
332 if self.labels.len() > default_labels.len() {
333 self.labels.clone_from(default_labels);
334 }
335
336 self.receive_contracts.clear();
337 self.fallback_contracts.clear();
338 self.non_fallback_contracts.clear();
339 self.functions_by_address.clear();
340 }
341
342 pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
346 self.collect_identified_addresses(self.identify_addresses(arena, identifier));
347 }
348
349 pub fn identify_addresses<'a>(
353 &self,
354 arena: &CallTraceArena,
355 identifier: &'a mut impl TraceIdentifier,
356 ) -> Vec<IdentifiedAddress<'a>> {
357 let nodes = arena.nodes().iter().filter(|node| {
358 if node.is_precompile()
360 || precompiles::is_known_precompile(
361 node.trace.address,
362 self.chain_id,
363 self.tempo_hardfork,
364 )
365 {
366 return false;
367 }
368 let address = &node.trace.address;
369 !self.labels.contains_key(address) || !self.contracts.contains_key(address)
370 });
371 identifier.identify_addresses(&nodes.collect::<Vec<_>>())
372 }
373
374 pub fn push_event(&mut self, event: Event) {
376 self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
377 }
378
379 pub fn push_function(&mut self, function: Function) {
381 let selector = function.selector();
382 let functions = self.functions.entry(selector).or_default();
383
384 if Self::push_function_to(functions, function) && functions.len() > 1 {
385 let function = functions.last().expect("function was just inserted");
386 let signature = function.signature();
387 trace!(target: "evm::traces", %selector, new=%signature, "duplicate function selector");
388 }
389 }
390
391 pub fn push_address_function(&mut self, address: Address, function: Function) {
393 let functions = self
394 .functions_by_address
395 .entry(address)
396 .or_default()
397 .entry(function.selector())
398 .or_default();
399 Self::push_function_to(functions, function);
400 }
401
402 fn push_function_to(functions: &mut Vec<Function>, function: Function) -> bool {
403 if functions.contains(&function) {
404 false
405 } else {
406 functions.push(function);
407 true
408 }
409 }
410
411 fn functions_for_selector(&self, address: Address, selector: &Selector) -> Option<&[Function]> {
412 self.functions_by_address
413 .get(&address)
414 .and_then(|functions| functions.get(selector))
415 .or_else(|| self.functions.get(selector))
416 .map(Vec::as_slice)
417 }
418
419 fn select_contract_function<'a>(
425 &self,
426 functions: &'a [Function],
427 trace: &CallTrace,
428 ) -> &'a [Function] {
429 if functions.len() > 1 {
433 for (i, func) in functions.iter().enumerate() {
434 if trace.data.len() >= SELECTOR_LEN
435 && func.abi_decode_input(&trace.data[SELECTOR_LEN..]).is_ok()
436 {
437 return &functions[i..i + 1];
438 }
439 }
440 }
441 functions
442 }
443
444 pub fn push_error(&mut self, error: Error) {
446 self.revert_decoder.push_error(error);
447 }
448
449 pub const fn without_label(&mut self, disable: bool) {
450 self.disable_labels = disable;
451 }
452
453 fn collect_identified_addresses(&mut self, mut addrs: Vec<IdentifiedAddress<'_>>) {
454 addrs.sort_by_key(|identity| identity.address);
455 addrs.dedup_by_key(|identity| identity.address);
456 if addrs.is_empty() {
457 return;
458 }
459
460 trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
461 for IdentifiedAddress { address, label, contract, abi, artifact_id: _ } in addrs {
462 let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
463
464 if let Some(contract) = contract {
465 self.contracts.entry(address).or_insert(contract);
466 }
467
468 if let Some(label) = label.filter(|s| !s.is_empty()) {
469 self.labels.entry(address).or_insert(label);
470 }
471
472 if let Some(abi) = abi {
473 self.collect_abi(&abi, Some(address));
474 }
475 }
476 }
477
478 fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>) {
479 let len = abi.len();
480 if len == 0 {
481 return;
482 }
483 trace!(target: "evm::traces", len, ?address, "collecting ABI");
484 for function in abi.functions() {
485 if let Some(address) = address {
486 self.push_address_function(address, function.clone());
487 }
488 self.push_function(function.clone());
489 }
490 for event in abi.events() {
491 self.push_event(event.clone());
492 }
493 for error in abi.errors() {
494 self.push_error(error.clone());
495 }
496 if let Some(address) = address {
497 if abi.receive.is_some() {
498 self.receive_contracts.insert(address);
499 }
500
501 if abi.fallback.is_some() {
502 self.fallback_contracts
503 .insert(address, abi.functions().map(|f| f.selector()).collect());
504 } else {
505 self.non_fallback_contracts
506 .insert(address, abi.functions().map(|f| f.selector()).collect());
507 }
508 }
509 }
510
511 pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
515 for node in traces {
516 node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await));
517 for log in &mut node.logs {
518 log.decoded = Some(Box::new(self.decode_event(&log.raw_log).await));
519 }
520
521 if let Some(debug) = self.debug_identifier.as_ref()
522 && let Some(identified) = self.contracts.get(&node.trace.address)
523 {
524 debug.identify_node_steps(node, get_contract_name(identified))
525 }
526 }
527 }
528
529 pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
531 let label =
532 if self.disable_labels { None } else { self.labels.get(&trace.address).cloned() };
533
534 if trace.kind.is_any_create() {
535 return DecodedCallTrace { label, ..Default::default() };
536 }
537
538 if let Some(trace) = precompiles::decode(trace, self.chain_id, self.tempo_hardfork) {
539 return trace;
540 }
541
542 let cdata = &trace.data;
543 if trace.address == DEFAULT_CREATE2_DEPLOYER {
544 return DecodedCallTrace {
545 label,
546 call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
547 return_data: self.default_return_data(trace),
548 };
549 }
550
551 if is_abi_call_data(cdata) {
552 let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
553 let mut identified_functions = Vec::new();
554 let functions = match self.functions_for_selector(trace.address, &selector) {
555 Some(functions) => functions,
556 None => {
557 if let Some(identifier) = &self.signature_identifier
558 && let Some(function) = identifier.identify_function(selector).await
559 {
560 identified_functions.push(function);
561 }
562 &identified_functions
563 }
564 };
565
566 if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address)
569 && !contract_selectors.contains(&selector)
570 && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address))
571 {
572 let return_data = if trace.success {
573 None
574 } else {
575 let revert_msg = self.revert_decoder.decode(&trace.output, trace.status);
576
577 if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") {
578 Some(format!(
579 "unrecognized function selector {} for contract {}, which has no fallback function.",
580 selector, trace.address
581 ))
582 } else {
583 Some(revert_msg)
584 }
585 };
586
587 return if let Some(func) = functions.first() {
588 DecodedCallTrace {
589 label,
590 call_data: Some(self.decode_function_input(trace, func)),
591 return_data,
592 }
593 } else {
594 DecodedCallTrace {
595 label,
596 call_data: self.fallback_call_data(trace),
597 return_data,
598 }
599 };
600 }
601
602 let contract_functions = self.select_contract_function(functions, trace);
603 let [func, ..] = contract_functions else {
604 return DecodedCallTrace {
605 label,
606 call_data: self.fallback_call_data(trace),
607 return_data: self.default_return_data(trace),
608 };
609 };
610
611 let mut call_data = self.decode_function_input(trace, func);
614 if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address)
615 && !fallback_functions.contains(&selector)
616 && let Some(cd) = self.fallback_call_data(trace)
617 {
618 call_data.signature = cd.signature;
619 }
620
621 DecodedCallTrace {
622 label,
623 call_data: Some(call_data),
624 return_data: self.decode_function_output(trace, contract_functions),
625 }
626 } else {
627 DecodedCallTrace {
628 label,
629 call_data: self.fallback_call_data(trace),
630 return_data: self.default_return_data(trace),
631 }
632 }
633 }
634
635 fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
637 let mut args = None;
638 if trace.data.len() >= SELECTOR_LEN {
639 if trace.address == CHEATCODE_ADDRESS {
640 if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
642 args = Some(v);
643 }
644 }
645
646 if args.is_none()
647 && let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..])
648 {
649 args = Some(v.iter().map(|value| self.format_value(value)).collect());
650 }
651 }
652
653 DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
654 }
655
656 fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
658 match func.name.as_str() {
659 "expectRevert" => {
660 let decoded = match data.get(SELECTOR_LEN..) {
661 Some(data) => func.abi_decode_input(data).ok(),
662 None => None,
663 };
664 let Some(decoded) = decoded else {
665 return Some(vec![self.revert_decoder.decode(data, None)]);
666 };
667 let Some(first) = decoded.first() else {
668 return Some(vec![self.revert_decoder.decode(data, None)]);
669 };
670 let expected_revert = match first {
671 DynSolValue::Bytes(bytes) => bytes.as_slice(),
672 DynSolValue::FixedBytes(word, size) => &word[..*size],
673 _ => return None,
674 };
675 Some(
676 std::iter::once(self.revert_decoder.decode(expected_revert, None))
677 .chain(decoded.iter().skip(1).map(|value| self.format_value(value)))
678 .collect(),
679 )
680 }
681 "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
682 Some(vec!["<pk>".to_string()])
684 }
685 "broadcast" | "startBroadcast" => {
686 (!func.inputs.is_empty() && func.inputs[0].ty == "uint256").then(|| vec!["<pk>".to_string()])
689 }
690 "getNonce" => {
691 (!func.inputs.is_empty() && func.inputs[0].ty == "tuple").then(|| vec!["<pk>".to_string()])
694 }
695 "sign" | "signP256" => {
696 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
697
698 if !decoded.is_empty() &&
701 (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
702 {
703 decoded[0] = DynSolValue::String("<pk>".to_string());
704 }
705
706 Some(decoded.iter().map(format_token).collect())
707 }
708 "signDelegation" | "signAndAttachDelegation" => {
709 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
710 decoded[1] = DynSolValue::String("<pk>".to_string());
714 Some(decoded.iter().map(format_token).collect())
715 }
716 "parseJson" |
717 "parseJsonUint" |
718 "parseJsonUintArray" |
719 "parseJsonInt" |
720 "parseJsonIntArray" |
721 "parseJsonString" |
722 "parseJsonStringArray" |
723 "parseJsonAddress" |
724 "parseJsonAddressArray" |
725 "parseJsonBool" |
726 "parseJsonBoolArray" |
727 "parseJsonBytes" |
728 "parseJsonBytesArray" |
729 "parseJsonBytes32" |
730 "parseJsonBytes32Array" |
731 "writeJson" |
732 "keyExists" |
734 "keyExistsJson" |
735 "serializeBool" |
736 "serializeUint" |
737 "serializeUintToHex" |
738 "serializeInt" |
739 "serializeAddress" |
740 "serializeBytes32" |
741 "serializeString" |
742 "serializeBytes" => {
743 if self.verbosity >= 5 {
744 None
745 } else {
746 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
747 let token = if func.name.as_str() == "parseJson" ||
748 func.name.as_str() == "keyExists" ||
750 func.name.as_str() == "keyExistsJson"
751 {
752 "<JSON file>"
753 } else {
754 "<stringified JSON>"
755 };
756 decoded[0] = DynSolValue::String(token.to_string());
757 Some(decoded.iter().map(format_token).collect())
758 }
759 }
760 s if s.contains("Toml") => {
761 if self.verbosity >= 5 {
762 None
763 } else {
764 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
765 let token = if func.name.as_str() == "parseToml" ||
766 func.name.as_str() == "keyExistsToml"
767 {
768 "<TOML file>"
769 } else {
770 "<stringified TOML>"
771 };
772 decoded[0] = DynSolValue::String(token.to_string());
773 Some(decoded.iter().map(format_token).collect())
774 }
775 }
776 "createFork" |
777 "createSelectFork" |
778 "rpc" => {
779 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
780
781 if !decoded.is_empty() && func.inputs[0].ty == "string" {
783 let url_or_alias = decoded[0].as_str().unwrap_or_default();
784
785 if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
786 decoded[0] = DynSolValue::String("<rpc url>".to_string());
787 }
788 } else {
789 return None;
790 }
791
792 Some(decoded.iter().map(format_token).collect())
793 }
794 _ => None,
795 }
796 }
797
798 fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
800 if !trace.success {
801 return self.default_return_data(trace);
802 }
803
804 if trace.address == CHEATCODE_ADDRESS
805 && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
806 {
807 return Some(decoded);
808 }
809
810 if let Some(values) =
811 funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok())
812 {
813 if values.is_empty() {
816 return None;
817 }
818
819 return Some(
820 values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
821 );
822 }
823
824 None
825 }
826
827 fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
829 match func.name.as_str() {
830 s if s.starts_with("env") => Some("<env var value>"),
831 "createWallet" | "deriveKey" => Some("<pk>"),
832 "promptSecret" | "promptSecretUint" => Some("<secret>"),
833 "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
834 "readFile" if self.verbosity < 5 => Some("<file>"),
835 "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
836 _ => None,
837 }
838 .map(Into::into)
839 }
840
841 #[track_caller]
842 fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
843 let cdata = &trace.data;
844 let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
845 "receive()"
846 } else if self.fallback_contracts.contains_key(&trace.address) {
847 "fallback()"
848 } else {
849 return None;
850 }
851 .to_string();
852 let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
853 Some(DecodedCallData { signature, args })
854 }
855
856 fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
858 if trace.status.is_none_or(|s| s.is_ok()) {
863 return None;
864 }
865 (!trace.success).then(|| self.revert_decoder.decode(&trace.output, trace.status))
866 }
867
868 pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
870 let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
871
872 let mut events = Vec::new();
873 let events = match self.events.get(&(t0, log.topics().len() - 1)) {
874 Some(es) => es,
875 None => {
876 if let Some(identifier) = &self.signature_identifier
877 && let Some(event) = identifier.identify_event(t0).await
878 {
879 events.push(get_indexed_event(event, log));
880 }
881 &events
882 }
883 };
884 for event in events {
885 if let Ok(decoded) = event.decode_log(log) {
886 let params = reconstruct_params(event, &decoded);
887 return DecodedCallLog {
888 name: Some(event.name.clone()),
889 params: Some(
890 params
891 .into_iter()
892 .zip(event.inputs.iter())
893 .map(|(param, input)| {
894 let name = input.name.clone();
896 (name, self.format_value(¶m))
897 })
898 .collect(),
899 ),
900 };
901 }
902 }
903
904 DecodedCallLog { name: None, params: None }
905 }
906
907 pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
909 let Some(identifier) = &self.signature_identifier else { return };
910 let events = nodes
911 .iter()
912 .flat_map(|node| {
913 node.logs
914 .iter()
915 .map(|log| log.raw_log.topics())
916 .filter(|&topics| {
917 if let Some(&first) = topics.first()
918 && self.events.contains_key(&(first, topics.len() - 1))
919 {
920 return false;
921 }
922 true
923 })
924 .filter_map(|topics| topics.first())
925 })
926 .copied();
927 let functions = nodes
928 .iter()
929 .filter(|&n| {
930 if n.trace.address == DEFAULT_CREATE2_DEPLOYER
932 || n.is_precompile()
933 || precompiles::is_known_precompile(
934 n.trace.address,
935 self.chain_id,
936 self.tempo_hardfork,
937 )
938 {
939 return false;
940 }
941 if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
943 return false;
944 }
945 true
946 })
947 .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
948 .filter(|selector| !self.functions.contains_key(selector));
949 let selectors = events
950 .map(SelectorKind::Event)
951 .chain(functions.map(SelectorKind::Function))
952 .unique()
953 .collect::<Vec<_>>();
954 let _ = identifier.identify(&selectors).await;
955 }
956
957 fn format_value(&self, value: &DynSolValue) -> String {
959 if let DynSolValue::Address(addr) = value
960 && let Some(label) = self.labels.get(addr)
961 {
962 return format!("{label}: [{addr}]");
963 }
964 format_token(value)
965 }
966}
967
968fn is_abi_call_data(data: &[u8]) -> bool {
972 match data.len().cmp(&SELECTOR_LEN) {
973 std::cmp::Ordering::Less => false,
974 std::cmp::Ordering::Equal => true,
975 std::cmp::Ordering::Greater => is_abi_data(&data[SELECTOR_LEN..]),
976 }
977}
978
979fn is_abi_data(data: &[u8]) -> bool {
983 let rem = data.len() % 32;
984 if rem == 0 || data.is_empty() {
985 return true;
986 }
987 data[data.len() - rem..].iter().all(|byte| *byte == 0)
989}
990
991fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
994 let mut indexed = 0;
995 let mut unindexed = 0;
996 let mut inputs = vec![];
997 for input in &event.inputs {
998 if input.indexed && indexed < decoded.indexed.len() {
1002 inputs.push(decoded.indexed[indexed].clone());
1003 indexed += 1;
1004 } else if unindexed < decoded.body.len() {
1005 inputs.push(decoded.body[unindexed].clone());
1006 unindexed += 1;
1007 }
1008 }
1009
1010 inputs
1011}
1012
1013fn indexed_inputs(event: &Event) -> usize {
1014 event.inputs.iter().filter(|param| param.indexed).count()
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019 use super::*;
1020 use alloy_primitives::{U256, address, aliases::U96, hex};
1021 use alloy_sol_types::{SolCall, SolEvent};
1022
1023 #[test]
1024 fn test_selector_collision_resolution() {
1025 use alloy_json_abi::Function;
1026 use alloy_primitives::Address;
1027
1028 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
1030 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
1031
1032 assert_eq!(func1.selector(), func2.selector());
1034
1035 let functions = vec![func1, func2];
1036
1037 let trace = CallTrace {
1039 address: Address::from([0x12; 20]),
1040 data: hex!("23b872dd000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000064").to_vec().into(),
1041 ..Default::default()
1042 };
1043
1044 let decoder = CallTraceDecoder::new();
1045 let result = decoder.select_contract_function(&functions, &trace);
1046
1047 assert_eq!(result.len(), 1);
1049 assert_eq!(result[0].signature(), "transferFrom(address,address,uint256)");
1050 }
1051
1052 #[test]
1053 fn test_selector_collision_resolution_second_function() {
1054 use alloy_json_abi::Function;
1055 use alloy_primitives::Address;
1056
1057 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
1059 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
1060
1061 let functions = vec![func1, func2];
1062
1063 let trace = CallTrace {
1065 address: Address::from([0x12; 20]),
1066 data: hex!("23b872dd0000000000000000000000000000000000000000000000000000000000000064")
1067 .to_vec()
1068 .into(),
1069 ..Default::default()
1070 };
1071
1072 let decoder = CallTraceDecoder::new();
1073 let result = decoder.select_contract_function(&functions, &trace);
1074
1075 assert_eq!(result.len(), 1);
1077 assert_eq!(result[0].signature(), "gasprice_bit_ether(int128)");
1078 }
1079
1080 #[test]
1081 fn test_should_redact() {
1082 let decoder = CallTraceDecoder::new();
1083
1084 let expected_revert_bytes4 = vec![0xde, 0xad, 0xbe, 0xef];
1085 let expect_revert_bytes4_data = Function::parse("expectRevert(bytes4)")
1086 .unwrap()
1087 .abi_encode_input(&[DynSolValue::FixedBytes(
1088 B256::right_padding_from(expected_revert_bytes4.as_slice()),
1089 4,
1090 )])
1091 .unwrap();
1092
1093 let expected_revert_bytes = hex!(
1094 "08c379a000000000000000000000000000000000000000000000000000000000\
1095 0000002000000000000000000000000000000000000000000000000000000000\
1096 00000004626f6f6d000000000000000000000000000000000000000000000000"
1097 )
1098 .to_vec();
1099 let expect_revert_bytes_data = Function::parse("expectRevert(bytes)")
1100 .unwrap()
1101 .abi_encode_input(&[DynSolValue::Bytes(expected_revert_bytes.clone())])
1102 .unwrap();
1103
1104 let reverter = Address::from([0x11; 20]);
1105 let expect_revert_bytes4_address_data = Function::parse("expectRevert(bytes4,address)")
1106 .unwrap()
1107 .abi_encode_input(&[
1108 DynSolValue::FixedBytes(
1109 B256::right_padding_from(expected_revert_bytes4.as_slice()),
1110 4,
1111 ),
1112 DynSolValue::Address(reverter),
1113 ])
1114 .unwrap();
1115
1116 let count = 42_u64;
1117 let expect_revert_bytes_count_data = Function::parse("expectRevert(bytes,uint64)")
1118 .unwrap()
1119 .abi_encode_input(&[
1120 DynSolValue::Bytes(expected_revert_bytes.clone()),
1121 DynSolValue::Uint(alloy_primitives::U256::from(count), 64),
1122 ])
1123 .unwrap();
1124
1125 let expect_revert_bytes_address_count_data =
1126 Function::parse("expectRevert(bytes,address,uint64)")
1127 .unwrap()
1128 .abi_encode_input(&[
1129 DynSolValue::Bytes(expected_revert_bytes.clone()),
1130 DynSolValue::Address(reverter),
1131 DynSolValue::Uint(alloy_primitives::U256::from(count), 64),
1132 ])
1133 .unwrap();
1134
1135 let expect_revert_runtime_data = expected_revert_bytes4.clone();
1136
1137 let cheatcode_input_test_cases = vec![
1139 (
1141 "expectRevert(bytes4)",
1142 expect_revert_bytes4_data,
1143 Some(vec![decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None)]),
1144 ),
1145 (
1146 "expectRevert(bytes)",
1147 expect_revert_bytes_data,
1148 Some(vec![decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None)]),
1149 ),
1150 (
1151 "expectRevert(bytes4)",
1152 expect_revert_runtime_data.clone(),
1153 Some(vec![
1154 decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None),
1155 ]),
1156 ),
1157 (
1158 "expectRevert(bytes4,address)",
1159 expect_revert_bytes4_address_data,
1160 Some(vec![
1161 decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None),
1162 decoder.format_value(&DynSolValue::Address(reverter)),
1163 ]),
1164 ),
1165 (
1166 "expectRevert(bytes,uint64)",
1167 expect_revert_bytes_count_data,
1168 Some(vec![
1169 decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None),
1170 decoder
1171 .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)),
1172 ]),
1173 ),
1174 (
1175 "expectRevert(bytes,address,uint64)",
1176 expect_revert_bytes_address_count_data,
1177 Some(vec![
1178 decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None),
1179 decoder.format_value(&DynSolValue::Address(reverter)),
1180 decoder
1181 .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)),
1182 ]),
1183 ),
1184 (
1185 "expectRevert()",
1186 expect_revert_runtime_data.clone(),
1187 Some(vec![
1188 decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None),
1189 ]),
1190 ),
1191 ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1193 ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
1194 ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1195 ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
1196 ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
1197 ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
1198 ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
1199 ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1200 ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1203 ("broadcast()", vec![], None), ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1205 ("startBroadcast()", vec![], None), ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
1207 ("getNonce(address)", vec![], None), (
1211 "sign(uint256,bytes32)",
1212 hex!(
1213 "
1214 e341eaa4
1215 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
1216 0000000000000000000000000000000000000000000000000000000000000000
1217 "
1218 )
1219 .to_vec(),
1220 Some(vec![
1221 "\"<pk>\"".to_string(),
1222 "0x0000000000000000000000000000000000000000000000000000000000000000"
1223 .to_string(),
1224 ]),
1225 ),
1226 (
1227 "signP256(uint256,bytes32)",
1228 hex!(
1229 "
1230 83211b40
1231 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
1232 0000000000000000000000000000000000000000000000000000000000000000
1233 "
1234 )
1235 .to_vec(),
1236 Some(vec![
1237 "\"<pk>\"".to_string(),
1238 "0x0000000000000000000000000000000000000000000000000000000000000000"
1239 .to_string(),
1240 ]),
1241 ),
1242 (
1243 "createFork(string)",
1245 hex!(
1246 "
1247 31ba3498
1248 0000000000000000000000000000000000000000000000000000000000000020
1249 000000000000000000000000000000000000000000000000000000000000002c
1250 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1251 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1252 "
1253 )
1254 .to_vec(),
1255 Some(vec!["\"<rpc url>\"".to_string()]),
1256 ),
1257 (
1258 "createFork(string)",
1260 hex!(
1261 "
1262 31ba3498
1263 0000000000000000000000000000000000000000000000000000000000000020
1264 000000000000000000000000000000000000000000000000000000000000002a
1265 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
1266 76322f6170695f6b657900000000000000000000000000000000000000000000
1267 "
1268 )
1269 .to_vec(),
1270 Some(vec!["\"<rpc url>\"".to_string()]),
1271 ),
1272 (
1273 "createFork(string)",
1275 hex!(
1276 "
1277 31ba3498
1278 0000000000000000000000000000000000000000000000000000000000000020
1279 0000000000000000000000000000000000000000000000000000000000000007
1280 6d61696e6e657400000000000000000000000000000000000000000000000000
1281 "
1282 )
1283 .to_vec(),
1284 Some(vec!["\"mainnet\"".to_string()]),
1285 ),
1286 (
1287 "createFork(string,uint256)",
1289 hex!(
1290 "
1291 6ba3ba2b
1292 0000000000000000000000000000000000000000000000000000000000000040
1293 0000000000000000000000000000000000000000000000000000000000000001
1294 000000000000000000000000000000000000000000000000000000000000002c
1295 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1296 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1297 "
1298 )
1299 .to_vec(),
1300 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1301 ),
1302 (
1303 "createFork(string,uint256)",
1305 hex!(
1306 "
1307 6ba3ba2b
1308 0000000000000000000000000000000000000000000000000000000000000040
1309 0000000000000000000000000000000000000000000000000000000000000001
1310 0000000000000000000000000000000000000000000000000000000000000007
1311 6d61696e6e657400000000000000000000000000000000000000000000000000
1312 "
1313 )
1314 .to_vec(),
1315 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1316 ),
1317 (
1318 "createFork(string,bytes32)",
1320 hex!(
1321 "
1322 7ca29682
1323 0000000000000000000000000000000000000000000000000000000000000040
1324 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1325 000000000000000000000000000000000000000000000000000000000000002c
1326 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1327 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1328 "
1329 )
1330 .to_vec(),
1331 Some(vec![
1332 "\"<rpc url>\"".to_string(),
1333 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1334 .to_string(),
1335 ]),
1336 ),
1337 (
1338 "createFork(string,bytes32)",
1341 hex!(
1342 "
1343 7ca29682
1344 0000000000000000000000000000000000000000000000000000000000000040
1345 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1346 0000000000000000000000000000000000000000000000000000000000000007
1347 6d61696e6e657400000000000000000000000000000000000000000000000000
1348 "
1349 )
1350 .to_vec(),
1351 Some(vec![
1352 "\"mainnet\"".to_string(),
1353 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1354 .to_string(),
1355 ]),
1356 ),
1357 (
1358 "createSelectFork(string)",
1360 hex!(
1361 "
1362 98680034
1363 0000000000000000000000000000000000000000000000000000000000000020
1364 000000000000000000000000000000000000000000000000000000000000002c
1365 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1366 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1367 "
1368 )
1369 .to_vec(),
1370 Some(vec!["\"<rpc url>\"".to_string()]),
1371 ),
1372 (
1373 "createSelectFork(string)",
1375 hex!(
1376 "
1377 98680034
1378 0000000000000000000000000000000000000000000000000000000000000020
1379 0000000000000000000000000000000000000000000000000000000000000007
1380 6d61696e6e657400000000000000000000000000000000000000000000000000
1381 "
1382 )
1383 .to_vec(),
1384 Some(vec!["\"mainnet\"".to_string()]),
1385 ),
1386 (
1387 "createSelectFork(string,uint256)",
1389 hex!(
1390 "
1391 71ee464d
1392 0000000000000000000000000000000000000000000000000000000000000040
1393 0000000000000000000000000000000000000000000000000000000000000001
1394 000000000000000000000000000000000000000000000000000000000000002c
1395 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1396 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1397 "
1398 )
1399 .to_vec(),
1400 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1401 ),
1402 (
1403 "createSelectFork(string,uint256)",
1405 hex!(
1406 "
1407 71ee464d
1408 0000000000000000000000000000000000000000000000000000000000000040
1409 0000000000000000000000000000000000000000000000000000000000000001
1410 0000000000000000000000000000000000000000000000000000000000000007
1411 6d61696e6e657400000000000000000000000000000000000000000000000000
1412 "
1413 )
1414 .to_vec(),
1415 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1416 ),
1417 (
1418 "createSelectFork(string,bytes32)",
1420 hex!(
1421 "
1422 84d52b7a
1423 0000000000000000000000000000000000000000000000000000000000000040
1424 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1425 000000000000000000000000000000000000000000000000000000000000002c
1426 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1427 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1428 "
1429 )
1430 .to_vec(),
1431 Some(vec![
1432 "\"<rpc url>\"".to_string(),
1433 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1434 .to_string(),
1435 ]),
1436 ),
1437 (
1438 "createSelectFork(string,bytes32)",
1441 hex!(
1442 "
1443 84d52b7a
1444 0000000000000000000000000000000000000000000000000000000000000040
1445 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1446 0000000000000000000000000000000000000000000000000000000000000007
1447 6d61696e6e657400000000000000000000000000000000000000000000000000
1448 "
1449 )
1450 .to_vec(),
1451 Some(vec![
1452 "\"mainnet\"".to_string(),
1453 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1454 .to_string(),
1455 ]),
1456 ),
1457 (
1458 "rpc(string,string,string)",
1460 hex!(
1461 "
1462 0199a220
1463 0000000000000000000000000000000000000000000000000000000000000060
1464 00000000000000000000000000000000000000000000000000000000000000c0
1465 0000000000000000000000000000000000000000000000000000000000000100
1466 000000000000000000000000000000000000000000000000000000000000002c
1467 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1468 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1469 000000000000000000000000000000000000000000000000000000000000000e
1470 6574685f67657442616c616e6365000000000000000000000000000000000000
1471 0000000000000000000000000000000000000000000000000000000000000034
1472 5b22307835353165373738343737386566386530343865343935646634396632
1473 363134663834613466316463222c22307830225d000000000000000000000000
1474 "
1475 )
1476 .to_vec(),
1477 Some(vec![
1478 "\"<rpc url>\"".to_string(),
1479 "\"eth_getBalance\"".to_string(),
1480 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1481 .to_string(),
1482 ]),
1483 ),
1484 (
1485 "rpc(string,string,string)",
1488 hex!(
1489 "
1490 0199a220
1491 0000000000000000000000000000000000000000000000000000000000000060
1492 00000000000000000000000000000000000000000000000000000000000000a0
1493 00000000000000000000000000000000000000000000000000000000000000e0
1494 0000000000000000000000000000000000000000000000000000000000000007
1495 6d61696e6e657400000000000000000000000000000000000000000000000000
1496 000000000000000000000000000000000000000000000000000000000000000e
1497 6574685f67657442616c616e6365000000000000000000000000000000000000
1498 0000000000000000000000000000000000000000000000000000000000000034
1499 5b22307835353165373738343737386566386530343865343935646634396632
1500 363134663834613466316463222c22307830225d000000000000000000000000
1501 "
1502 )
1503 .to_vec(),
1504 Some(vec![
1505 "\"mainnet\"".to_string(),
1506 "\"eth_getBalance\"".to_string(),
1507 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1508 .to_string(),
1509 ]),
1510 ),
1511 ];
1512
1513 let cheatcode_output_test_cases = vec![
1515 ("createWallet(string)", Some("<pk>".to_string())),
1517 ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1518 ("rpcUrl(string)", Some("<rpc url>".to_string())),
1520 ("rpcUrls()", Some("<rpc url>".to_string())),
1521 ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1522 ];
1523
1524 for (function_signature, data, expected) in cheatcode_input_test_cases {
1525 let function = Function::parse(function_signature).unwrap();
1526 let result = decoder.decode_cheatcode_inputs(&function, &data);
1527 assert_eq!(result, expected, "Input case failed for: {function_signature}");
1528 }
1529
1530 for (function_signature, expected) in cheatcode_output_test_cases {
1531 let function = Function::parse(function_signature).unwrap();
1532 let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1533 assert_eq!(result, expected, "Output case failed for: {function_signature}");
1534 }
1535 }
1536
1537 #[tokio::test]
1538 async fn test_tempo_decode_preserves_existing_labels() {
1539 let decoder = CallTraceDecoder::new();
1540 let trace = CallTrace { address: PATH_USD_ADDRESS, success: true, ..Default::default() };
1541
1542 let decoded = decoder.decode_function(&trace).await;
1543 assert_eq!(decoded.label.as_deref(), Some("PathUSD"));
1544 }
1545
1546 #[tokio::test]
1547 async fn test_t5_decode_does_not_synthesize_general_target_label() {
1548 let mut decoder = CallTraceDecoder::new().clone();
1549 decoder.chain_id = Some(4217);
1550 let trace = CallTrace {
1551 address: address!("0x0000000000000000000000000000000000000123"),
1552 depth: 0,
1553 success: true,
1554 ..Default::default()
1555 };
1556
1557 let decoded = decoder.decode_function(&trace).await;
1558 assert_eq!(decoded.label, None);
1559 }
1560
1561 #[tokio::test]
1562 async fn test_t5_tip20_logo_uri_calls_and_events_decode() {
1563 let decoder = CallTraceDecoder::new();
1564
1565 let call = ITIP20::setLogoURICall { newLogoURI: "https://example.com/logo.png".into() };
1566 let trace = CallTrace {
1567 address: PATH_USD_ADDRESS,
1568 data: call.abi_encode().into(),
1569 success: true,
1570 ..Default::default()
1571 };
1572 let decoded = decoder.decode_function(&trace).await;
1573 let call_data = decoded.call_data.expect("setLogoURI should decode");
1574 assert_eq!(call_data.signature, "setLogoURI(string)");
1575 assert_eq!(call_data.args, vec!["\"https://example.com/logo.png\"".to_string()]);
1576
1577 let call = ITIP20::logoURICall {};
1578 let trace = CallTrace {
1579 address: PATH_USD_ADDRESS,
1580 data: call.abi_encode().into(),
1581 success: true,
1582 ..Default::default()
1583 };
1584 let decoded = decoder.decode_function(&trace).await;
1585 assert_eq!(decoded.call_data.expect("logoURI should decode").signature, "logoURI()");
1586
1587 let event = ITIP20::LogoURIUpdated {
1588 updater: address!("0x0000000000000000000000000000000000000abc"),
1589 newLogoURI: "ipfs://logo".into(),
1590 };
1591 let decoded = decoder.decode_event(&event.encode_log_data()).await;
1592 assert_eq!(decoded.name.as_deref(), Some("LogoURIUpdated"));
1593 let params = decoded.params.expect("LogoURIUpdated params should decode");
1594 assert_eq!(params[0].0, "updater");
1595 assert!(
1596 params[0].1.to_ascii_lowercase().contains("0000000000000000000000000000000000000abc")
1597 );
1598 assert_eq!(params[1], ("newLogoURI".into(), "\"ipfs://logo\"".into()));
1599 }
1600
1601 #[tokio::test]
1602 async fn test_t5_tip20_factory_create_token_with_logo_decodes() {
1603 let decoder = CallTraceDecoder::new();
1604 let call = ITIP20Factory::createToken_1Call {
1605 name: "Example USD".into(),
1606 symbol: "xUSD".into(),
1607 currency: "USD".into(),
1608 quoteToken: PATH_USD_ADDRESS,
1609 admin: address!("0x0000000000000000000000000000000000000abc"),
1610 salt: B256::repeat_byte(0x11),
1611 logoURI: "https://example.com/xusd.png".into(),
1612 };
1613 let trace = CallTrace {
1614 address: TIP20_FACTORY_ADDRESS,
1615 data: call.abi_encode().into(),
1616 success: true,
1617 ..Default::default()
1618 };
1619 let decoded = decoder.decode_function(&trace).await;
1620 let call_data = decoded.call_data.expect("createToken overload should decode");
1621 assert_eq!(
1622 call_data.signature,
1623 "createToken(string,string,string,address,address,bytes32,string)"
1624 );
1625 assert_eq!(call_data.args[6], "\"https://example.com/xusd.png\"");
1626 }
1627
1628 #[tokio::test]
1629 async fn test_t5_stablecoin_dex_order_flipped_event_decodes() {
1630 let decoder = CallTraceDecoder::new();
1631 let event = IStablecoinDEX::OrderFlipped {
1632 orderId: 42,
1633 maker: address!("0x0000000000000000000000000000000000000abc"),
1634 token: PATH_USD_ADDRESS,
1635 amount: 1_000_000,
1636 isBid: false,
1637 tick: 100,
1638 flipTick: 100,
1639 };
1640 let decoded = decoder.decode_event(&event.encode_log_data()).await;
1641 assert_eq!(decoded.name.as_deref(), Some("OrderFlipped"));
1642 let params = decoded.params.expect("OrderFlipped params should decode");
1643 assert_eq!(params[0], ("orderId".into(), "42".into()));
1644 assert_eq!(params[4], ("isBid".into(), "false".into()));
1645 assert_eq!(params[5], ("tick".into(), "100".into()));
1646 assert_eq!(params[6], ("flipTick".into(), "100".into()));
1647 }
1648
1649 #[tokio::test]
1650 async fn test_t5_channel_reserve_call_and_event_decode() {
1651 let mut decoder = CallTraceDecoder::new().clone();
1652 decoder.chain_id = Some(4217);
1653
1654 let open = ITIP20ChannelReserve::openCall {
1655 payee: address!("0x0000000000000000000000000000000000000abc"),
1656 operator: Address::ZERO,
1657 token: PATH_USD_ADDRESS,
1658 deposit: U96::from(1_000_000u64),
1659 salt: B256::repeat_byte(0x22),
1660 authorizedSigner: Address::ZERO,
1661 };
1662 let trace = CallTrace {
1663 address: TIP20_CHANNEL_RESERVE_ADDRESS,
1664 data: open.abi_encode().into(),
1665 depth: 0,
1666 success: true,
1667 ..Default::default()
1668 };
1669 let decoded = decoder.decode_function(&trace).await;
1670 assert_eq!(decoded.label.as_deref(), Some("TIP20ChannelReserve"));
1671 assert_eq!(
1672 decoded.call_data.expect("open should decode").signature,
1673 "open(address,address,address,uint96,bytes32,address)"
1674 );
1675
1676 let transfer = ITIP20::transferCall {
1677 to: address!("0x0000000000000000000000000000000000000def"),
1678 amount: U256::from(1_000_000u64),
1679 };
1680 let trace = CallTrace {
1681 address: PATH_USD_ADDRESS,
1682 data: transfer.abi_encode().into(),
1683 depth: 0,
1684 success: true,
1685 ..Default::default()
1686 };
1687 let decoded = decoder.decode_function(&trace).await;
1688 assert_eq!(decoded.label.as_deref(), Some("PathUSD"));
1689 let json = serde_json::to_string(&decoded).expect("decoded trace serializes");
1690 assert!(json.contains(r#""label":"PathUSD""#));
1691 assert!(!json.contains("payment-lane"));
1692
1693 let balance_of = ITIP20::balanceOfCall {
1694 account: address!("0x0000000000000000000000000000000000000def"),
1695 };
1696 let trace = CallTrace {
1697 address: PATH_USD_ADDRESS,
1698 data: balance_of.abi_encode().into(),
1699 depth: 0,
1700 success: true,
1701 ..Default::default()
1702 };
1703 let decoded = decoder.decode_function(&trace).await;
1704 assert_eq!(decoded.label.as_deref(), Some("PathUSD"));
1705
1706 let event = ITIP20ChannelReserve::ChannelOpened {
1707 channelId: B256::repeat_byte(0x33),
1708 payer: address!("0x0000000000000000000000000000000000000123"),
1709 payee: address!("0x0000000000000000000000000000000000000abc"),
1710 operator: Address::ZERO,
1711 token: PATH_USD_ADDRESS,
1712 authorizedSigner: Address::ZERO,
1713 salt: B256::repeat_byte(0x22),
1714 expiringNonceHash: B256::repeat_byte(0x44),
1715 deposit: U96::from(1_000_000u64),
1716 };
1717 let decoded = decoder.decode_event(&event.encode_log_data()).await;
1718 assert_eq!(decoded.name.as_deref(), Some("ChannelOpened"));
1719 let params = decoded.params.expect("ChannelOpened params should decode");
1720 assert_eq!(params[0].0, "channelId");
1721 assert_eq!(params[8].0, "deposit");
1722 assert!(params[8].1.starts_with("1000000"));
1723 }
1724
1725 struct RecordingIdentifier {
1727 queried: Vec<Address>,
1728 }
1729 impl TraceIdentifier for RecordingIdentifier {
1730 fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec<IdentifiedAddress<'_>> {
1731 self.queried.extend(nodes.iter().map(|n| n.trace.address));
1732 Vec::new()
1733 }
1734 }
1735
1736 #[test]
1737 fn test_identify_addresses_skips_evm_precompiles() {
1738 use foundry_evm_core::precompiles::SHA_256;
1739
1740 let decoder = CallTraceDecoder::new();
1741
1742 let mut arena = CallTraceArena::default();
1743 let regular_addr = Address::from([0x42; 20]);
1744 arena.nodes_mut()[0].trace.address = regular_addr;
1745
1746 arena.nodes_mut().push(CallTraceNode {
1748 trace: CallTrace {
1749 address: SHA_256,
1750 depth: 1,
1751 maybe_precompile: Some(true),
1752 ..Default::default()
1753 },
1754 idx: 1,
1755 ..Default::default()
1756 });
1757
1758 arena.nodes_mut().push(CallTraceNode {
1760 trace: CallTrace {
1761 address: SHA_256,
1762 depth: 1,
1763 maybe_precompile: None,
1764 ..Default::default()
1765 },
1766 idx: 2,
1767 ..Default::default()
1768 });
1769
1770 let mut identifier = RecordingIdentifier { queried: Vec::new() };
1771 decoder.identify_addresses(&arena, &mut identifier);
1772
1773 assert_eq!(identifier.queried, vec![regular_addr]);
1774 }
1775
1776 #[test]
1777 fn test_identify_addresses_skips_tempo_precompiles() {
1778 use foundry_evm_core::tempo::{TEMPO_PRECOMPILE_ADDRESSES, TIP20_CHANNEL_RESERVE_ADDRESS};
1779
1780 let decoder = CallTraceDecoderBuilder::new()
1782 .with_chain_id(Some(4217))
1783 .with_tempo_hardfork(Some(TempoHardfork::T5))
1784 .build();
1785
1786 assert_eq!(
1787 decoder.labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS),
1788 Some(&"TIP20ChannelReserve".to_string())
1789 );
1790
1791 let mut arena = CallTraceArena::default();
1792 let regular_addr = Address::from([0x42; 20]);
1793 arena.nodes_mut()[0].trace.address = regular_addr;
1794
1795 let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0];
1798 arena.nodes_mut().push(CallTraceNode {
1799 trace: CallTrace {
1800 address: tempo_precompile,
1801 depth: 1,
1802 maybe_precompile: None,
1803 ..Default::default()
1804 },
1805 idx: 1,
1806 ..Default::default()
1807 });
1808
1809 let mut identifier = RecordingIdentifier { queried: Vec::new() };
1810 decoder.identify_addresses(&arena, &mut identifier);
1811
1812 assert_eq!(identifier.queried, vec![regular_addr]);
1814 }
1815
1816 #[test]
1817 fn test_tempo_hardfork_labels_do_not_clobber_user_labels() {
1818 use foundry_evm_core::tempo::TIP20_CHANNEL_RESERVE_ADDRESS;
1819
1820 let reserve_label = "UserReserve".to_string();
1821 let guard_label = "UserGuard".to_string();
1822 let decoder = CallTraceDecoderBuilder::new()
1823 .with_labels([
1824 (TIP20_CHANNEL_RESERVE_ADDRESS, reserve_label.clone()),
1825 (RECEIVE_POLICY_GUARD_ADDRESS, guard_label.clone()),
1826 ])
1827 .with_tempo_hardfork(Some(TempoHardfork::T6))
1828 .build();
1829
1830 assert_eq!(decoder.labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS), Some(&reserve_label));
1831 assert_eq!(decoder.labels.get(&RECEIVE_POLICY_GUARD_ADDRESS), Some(&guard_label));
1832 }
1833
1834 #[test]
1835 fn test_tempo_hardfork_none_does_not_remove_user_reserve_label() {
1836 use foundry_evm_core::tempo::TIP20_CHANNEL_RESERVE_ADDRESS;
1837
1838 let reserve_label = "UserReserve".to_string();
1839 let decoder = CallTraceDecoderBuilder::new()
1840 .with_labels([(TIP20_CHANNEL_RESERVE_ADDRESS, reserve_label.clone())])
1841 .with_tempo_hardfork(None)
1842 .build();
1843
1844 assert_eq!(decoder.labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS), Some(&reserve_label));
1845 }
1846
1847 #[tokio::test]
1848 async fn test_decode_receive_policy_guard_at_t6() {
1849 let function = Function::parse("claim(address,bytes)").unwrap();
1850 let data = function
1851 .abi_encode_input(&[
1852 DynSolValue::Address(Address::from([0x11; 20])),
1853 DynSolValue::Bytes(vec![0x12, 0x34]),
1854 ])
1855 .unwrap();
1856 let trace = CallTrace {
1857 address: RECEIVE_POLICY_GUARD_ADDRESS,
1858 data: data.into(),
1859 success: true,
1860 ..Default::default()
1861 };
1862
1863 let decoder = CallTraceDecoderBuilder::new()
1864 .with_chain_id(Some(4217))
1865 .with_tempo_hardfork(Some(TempoHardfork::T6))
1866 .build();
1867 let decoded = decoder.decode_function(&trace).await;
1868
1869 assert_eq!(decoded.label, Some("ReceivePolicyGuard".to_string()));
1870 assert_eq!(decoded.call_data.unwrap().signature, "claim(address,bytes)");
1871 }
1872
1873 #[test]
1874 fn test_identify_addresses_does_not_skip_future_tempo_precompiles() {
1875 use foundry_evm_core::tempo::TIP20_CHANNEL_RESERVE_ADDRESS;
1876
1877 let decoder = CallTraceDecoderBuilder::new()
1878 .with_chain_id(Some(4217))
1879 .with_tempo_hardfork(Some(TempoHardfork::T4))
1880 .build();
1881
1882 let mut arena = CallTraceArena::default();
1883 let regular_addr = Address::from([0x42; 20]);
1884 arena.nodes_mut()[0].trace.address = regular_addr;
1885
1886 arena.nodes_mut().push(CallTraceNode {
1887 trace: CallTrace {
1888 address: TIP20_CHANNEL_RESERVE_ADDRESS,
1889 depth: 1,
1890 maybe_precompile: None,
1891 ..Default::default()
1892 },
1893 idx: 1,
1894 ..Default::default()
1895 });
1896
1897 let mut identifier = RecordingIdentifier { queried: Vec::new() };
1898 decoder.identify_addresses(&arena, &mut identifier);
1899
1900 assert_eq!(identifier.queried, vec![regular_addr, TIP20_CHANNEL_RESERVE_ADDRESS]);
1901 }
1902
1903 #[test]
1904 fn test_identify_addresses_does_not_skip_tempo_precompiles_on_other_chains() {
1905 use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES;
1906
1907 let mut decoder = CallTraceDecoder::new().clone();
1909 decoder.chain_id = Some(1);
1910
1911 let mut arena = CallTraceArena::default();
1912 let regular_addr = Address::from([0x42; 20]);
1913 arena.nodes_mut()[0].trace.address = regular_addr;
1914
1915 let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0];
1916 arena.nodes_mut().push(CallTraceNode {
1917 trace: CallTrace {
1918 address: tempo_precompile,
1919 depth: 1,
1920 maybe_precompile: None,
1921 ..Default::default()
1922 },
1923 idx: 1,
1924 ..Default::default()
1925 });
1926
1927 let mut identifier = RecordingIdentifier { queried: Vec::new() };
1928 decoder.identify_addresses(&arena, &mut identifier);
1929
1930 assert_eq!(identifier.queried, vec![regular_addr, tempo_precompile]);
1932 }
1933}