1use crate::{
2 CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, DecodedTraceStep,
3 debug::DebugTraceIdentifier,
4 erc8021,
5 identifier::{IdentifiedAddress, LocalTraceIdentifier, SignaturesIdentifier, TraceIdentifier},
6};
7use alloy_dyn_abi::{DynSolValue, EventExt, FunctionExt, JsonAbiExt};
8use alloy_json_abi::{Constructor, Error, Event, Function, JsonAbi};
9use alloy_primitives::{
10 Address, B256, LogData, Selector, U256,
11 map::{AddressHashMap, HashMap, HashSet},
12};
13use alloy_sol_types::SolValue;
14use foundry_common::{
15 ContractsByArtifact, SELECTOR_LEN,
16 abi::{get_indexed_event, reconstruct_event_params},
17 fmt::format_token,
18 get_contract_name,
19 selectors::SelectorKind,
20 tempo::OUSD_ADDRESS,
21};
22use foundry_config::TracingConfig;
23use foundry_evm_core::{
24 abi::console,
25 constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS},
26 decode::RevertDecoder,
27 precompiles::{
28 BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1,
29 BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, CELO_TRANSFER, EC_ADD, EC_MUL, EC_PAIRING,
30 EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, RIPEMD_160, SHA_256,
31 },
32};
33use foundry_evm_hardforks::{ExecutionSpec, FoundryHardfork, TempoHardfork};
34use foundry_evm_networks::{
35 NetworkConfigs, NetworkVariant, TEMPO_PRECOMPILES, celo::transfer::CELO_TRANSFER_LABEL,
36};
37use itertools::Itertools;
38use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult};
39use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace};
40use std::{collections::BTreeMap, sync::OnceLock};
41use tempo_contracts::precompiles::{
42 CURRENT_COMMITTEE_ADDRESS, IAccountKeychain, IAddressRegistry, ICurrentCommittee, IFeeManager,
43 IReceivePolicyGuard, ISignatureVerifier, IStablecoinDEX, IStorageCredits, ITIP20ChannelReserve,
44 ITIP20Factory, ITIP403Registry, IValidatorConfig,
45};
46use tempo_precompiles::{
47 PATH_USD_ADDRESS, RECEIVE_POLICY_GUARD_ADDRESS, TIP403_REGISTRY_ADDRESS, nonce::INonce,
48 tip20::ITIP20,
49};
50
51#[cfg(feature = "base")]
52use base_common_precompiles::{
53 ActivationRegistryStorage, B20FactoryStorage, NonceManagerStorage, PolicyRegistryStorage,
54 TxContextStorage,
55};
56#[cfg(feature = "base")]
57use foundry_evm_hardforks::BaseSpecId;
58#[cfg(feature = "base")]
59use foundry_evm_networks::{active_base_precompiles, is_base_precompile_active_at};
60
61#[cfg(feature = "monad")]
62use foundry_evm_hardforks::MonadHardfork;
63
64#[cfg(feature = "base")]
65mod base;
66#[cfg(feature = "monad")]
67mod monad;
68pub(crate) mod precompiles;
69mod vm;
70
71type AddressEvents = HashMap<Address, BTreeMap<(Option<B256>, usize), Vec<Event>>>;
74
75#[derive(Debug, Default)]
77pub struct DecodedEvent {
78 pub name: Option<String>,
80 pub params: Option<Vec<(String, String, DynSolValue)>>,
82}
83
84impl DecodedEvent {
85 fn into_call_log(self) -> DecodedCallLog {
86 DecodedCallLog {
87 name: self.name,
88 params: self.params.map(|params| {
89 params.into_iter().map(|(name, display, _)| (name, display)).collect()
90 }),
91 }
92 }
93}
94
95#[derive(Default)]
97#[must_use = "builders do nothing unless you call `build` on them"]
98pub struct CallTraceDecoderBuilder {
99 decoder: CallTraceDecoder,
100}
101
102impl CallTraceDecoderBuilder {
103 #[inline]
105 pub fn new() -> Self {
106 Self { decoder: CallTraceDecoder::new().clone() }
107 }
108
109 #[inline]
111 pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
112 self.decoder.labels.extend(labels);
113 self
114 }
115
116 #[inline]
118 pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
119 self.decoder.collect_abi(abi, None, true);
120 self
121 }
122
123 #[inline]
125 pub fn with_address_events(mut self, address: Address, abi: &JsonAbi) -> Self {
126 for event in abi.events() {
127 self.decoder.push_address_event(address, event.clone());
128 }
129 self
130 }
131
132 #[inline]
134 pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self {
135 trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs");
136 for contract in contracts.values() {
137 self.decoder.collect_abi(&contract.abi, None, true);
138 }
139 self
140 }
141
142 #[inline]
144 pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
145 self.with_known_contracts(identifier.contracts())
146 }
147
148 #[inline]
150 pub const fn with_verbosity(mut self, level: u8) -> Self {
151 self.decoder.verbosity = level;
152 self
153 }
154
155 #[inline]
157 pub fn with_tracing_config(mut self, config: &TracingConfig) -> Self {
158 self.decoder.labels.extend(config.labels.clone());
159 self.decoder.verbosity = config.verbosity;
160 self.decoder.disable_labels = config.disable_labels;
161 self.decoder.compact_labels = config.compact_labels;
162 self
163 }
164
165 #[inline]
167 pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
168 self.decoder.signature_identifier = Some(identifier);
169 self
170 }
171
172 #[inline]
174 pub const fn with_label_disabled(mut self, disable_alias: bool) -> Self {
175 self.decoder.disable_labels = disable_alias;
176 self
177 }
178
179 #[inline]
181 pub const fn with_chain_id(mut self, chain_id: Option<u64>) -> Self {
182 self.decoder.chain_id = chain_id;
183 self
184 }
185
186 #[inline]
188 pub fn with_execution_network(self, network: NetworkVariant) -> Self {
189 self.with_networks(network.into())
190 }
191
192 #[inline]
194 pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
195 self.decoder.networks = Some(networks);
196 self
197 }
198
199 #[inline]
201 pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
202 self.decoder.hardfork = hardfork;
203 self
204 }
205
206 #[inline]
208 pub const fn with_compact_labels(mut self, compact: bool) -> Self {
209 self.decoder.compact_labels = compact;
210 self
211 }
212
213 #[inline]
215 pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
216 self.decoder.debug_identifier = Some(identifier);
217 self
218 }
219
220 #[inline]
222 pub fn build(mut self) -> CallTraceDecoder {
223 self.decoder.base_labels = self.decoder.labels.clone();
224 self.decoder.register_celo_metadata();
225 #[cfg(feature = "monad")]
226 self.decoder.register_monad_metadata();
227 #[cfg(feature = "base")]
228 self.decoder.register_base_metadata();
229 self.decoder
230 }
231}
232
233#[derive(Clone, Debug, Default)]
241pub struct CallTraceDecoder {
242 pub contracts: HashMap<Address, String>,
246 pub labels: HashMap<Address, String>,
248 base_labels: HashMap<Address, String>,
250 pub receive_contracts: HashSet<Address>,
252 pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
255 pub non_fallback_contracts: HashMap<Address, HashSet<Selector>>,
258
259 pub functions: HashMap<Selector, Vec<Function>>,
261 pub functions_by_address: HashMap<Address, HashMap<Selector, Vec<Function>>>,
263 pub constructors_by_address: HashMap<Address, Constructor>,
265 pub constructor_args_offsets: HashMap<Address, usize>,
267 pub events: BTreeMap<(B256, usize), Vec<Event>>,
271 events_by_address: Option<Box<AddressEvents>>,
273 pub revert_decoder: RevertDecoder,
275
276 pub signature_identifier: Option<SignaturesIdentifier>,
278 pub verbosity: u8,
280
281 pub debug_identifier: Option<DebugTraceIdentifier>,
283
284 pub disable_labels: bool,
286
287 pub chain_id: Option<u64>,
289
290 networks: Option<NetworkConfigs>,
292
293 pub opcodes: Vec<OpCode>,
295
296 hardfork: Option<FoundryHardfork>,
298
299 pub compact_labels: bool,
301}
302
303impl CallTraceDecoder {
304 fn register_celo_metadata(&mut self) {
305 if precompiles::is_known_precompile(
306 CELO_TRANSFER,
307 self.networks,
308 self.chain_id,
309 self.hardfork,
310 ) {
311 self.labels.entry(CELO_TRANSFER).or_insert_with(|| CELO_TRANSFER_LABEL.to_string());
312 }
313 }
314
315 #[cfg(feature = "base")]
316 fn register_base_metadata(&mut self) {
317 if self.networks.is_some_and(|networks| !networks.is_base()) {
318 return;
319 }
320 let Some(upgrade) =
321 self.hardfork.and_then(BaseSpecId::from_foundry_hardfork).map(|spec| spec.upgrade())
322 else {
323 return;
324 };
325
326 for (label, address) in active_base_precompiles(upgrade) {
327 self.labels.entry(address).or_insert_with(|| label.to_string());
328 }
329
330 if is_base_precompile_active_at(ActivationRegistryStorage::ADDRESS, upgrade) {
333 self.register_address_abi(
334 ActivationRegistryStorage::ADDRESS,
335 &base::IActivationRegistry::abi::contract(),
336 );
337 }
338 if is_base_precompile_active_at(PolicyRegistryStorage::ADDRESS, upgrade) {
339 self.register_address_abi(
340 PolicyRegistryStorage::ADDRESS,
341 &base::IPolicyRegistry::abi::contract(),
342 );
343 }
344 if is_base_precompile_active_at(B20FactoryStorage::ADDRESS, upgrade) {
345 self.register_address_abi(
346 B20FactoryStorage::ADDRESS,
347 &base::IB20Factory::abi::contract(),
348 );
349 }
350 if is_base_precompile_active_at(NonceManagerStorage::ADDRESS, upgrade) {
351 self.register_address_abi(
352 NonceManagerStorage::ADDRESS,
353 &base::INonceManager::abi::contract(),
354 );
355 }
356 if is_base_precompile_active_at(TxContextStorage::ADDRESS, upgrade) {
357 self.register_address_abi(
358 TxContextStorage::ADDRESS,
359 &base::ITransactionContext::abi::contract(),
360 );
361 }
362 }
363
364 pub fn new() -> &'static Self {
369 static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
372 INIT.get_or_init(Self::init)
373 }
374
375 pub const fn hardfork(&self) -> Option<FoundryHardfork> {
377 self.hardfork
378 }
379
380 pub fn set_hardfork(&mut self, hardfork: Option<FoundryHardfork>) {
385 if self.hardfork == hardfork {
386 return;
387 }
388 self.hardfork = hardfork;
389 self.clear_addresses();
390 }
391
392 #[instrument(name = "CallTraceDecoder::init", level = "debug")]
393 fn init() -> Self {
394 let tempo_abis = [
396 IFeeManager::abi::contract(),
397 ITIP20::abi::contract(),
398 ITIP403Registry::abi::contract(),
399 ITIP20Factory::abi::contract(),
400 IStablecoinDEX::abi::contract(),
401 IStorageCredits::abi::contract(),
402 INonce::abi::contract(),
403 IValidatorConfig::abi::contract(),
404 IAccountKeychain::abi::contract(),
405 IAddressRegistry::abi::contract(),
406 ITIP20ChannelReserve::abi::contract(),
407 ISignatureVerifier::abi::contract(),
408 IReceivePolicyGuard::abi::contract(),
409 ];
410 let mut labels = HashMap::from_iter([
411 (CHEATCODE_ADDRESS, "VM".to_string()),
412 (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
413 (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
414 (CALLER, "DefaultSender".to_string()),
415 (EC_RECOVER, "ECRecover".to_string()),
416 (SHA_256, "SHA-256".to_string()),
417 (RIPEMD_160, "RIPEMD-160".to_string()),
418 (IDENTITY, "Identity".to_string()),
419 (MOD_EXP, "ModExp".to_string()),
420 (EC_ADD, "ECAdd".to_string()),
421 (EC_MUL, "ECMul".to_string()),
422 (EC_PAIRING, "ECPairing".to_string()),
423 (BLAKE_2F, "Blake2F".to_string()),
424 (POINT_EVALUATION, "PointEvaluation".to_string()),
425 (BLS12_G1ADD, "BLS12_G1ADD".to_string()),
426 (BLS12_G1MSM, "BLS12_G1MSM".to_string()),
427 (BLS12_G2ADD, "BLS12_G2ADD".to_string()),
428 (BLS12_G2MSM, "BLS12_G2MSM".to_string()),
429 (BLS12_PAIRING_CHECK, "BLS12_PAIRING_CHECK".to_string()),
430 (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()),
431 (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()),
432 (PATH_USD_ADDRESS, "PathUSD".to_string()),
434 (OUSD_ADDRESS, "OUSD".to_string()),
435 ]);
436 labels.extend(
438 TEMPO_PRECOMPILES
439 .iter()
440 .filter(|(_, address)| *address != CURRENT_COMMITTEE_ADDRESS)
441 .map(|(label, address)| (*address, (*label).to_string())),
442 );
443
444 let functions = console::hh::abi::functions()
445 .into_values()
446 .chain([vm::functions()])
447 .chain(IFeeManager::abi::functions().into_values())
448 .chain(IStorageCredits::abi::functions().into_values())
451 .chain(ITIP20::abi::functions().into_values())
452 .chain(ITIP403Registry::abi::functions().into_values())
453 .chain(ITIP20Factory::abi::functions().into_values())
454 .chain(IStablecoinDEX::abi::functions().into_values())
455 .chain(INonce::abi::functions().into_values())
456 .chain(IValidatorConfig::abi::functions().into_values())
457 .chain(IAccountKeychain::abi::functions().into_values())
458 .chain(IAddressRegistry::abi::functions().into_values())
459 .chain(ITIP20ChannelReserve::abi::functions().into_values())
460 .chain(ISignatureVerifier::abi::functions().into_values())
461 .chain(IReceivePolicyGuard::abi::functions().into_values())
462 .flatten()
463 .map(|func| (func.selector(), vec![func]))
464 .collect();
465
466 let events = console::ds::abi::events()
467 .into_values()
468 .chain(IFeeManager::abi::events().into_values())
469 .chain(ITIP20::abi::events().into_values())
470 .chain(ITIP403Registry::abi::events().into_values())
471 .chain(ITIP20Factory::abi::events().into_values())
472 .chain(IStablecoinDEX::abi::events().into_values())
473 .chain(INonce::abi::events().into_values())
474 .chain(IValidatorConfig::abi::events().into_values())
475 .chain(IAccountKeychain::abi::events().into_values())
476 .chain(IAddressRegistry::abi::events().into_values())
477 .chain(ITIP20ChannelReserve::abi::events().into_values())
478 .chain(ISignatureVerifier::abi::events().into_values())
479 .chain(IReceivePolicyGuard::abi::events().into_values())
480 .flatten()
481 .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
482 .collect();
483
484 Self {
485 contracts: Default::default(),
486 labels,
487 base_labels: Default::default(),
488 receive_contracts: Default::default(),
489 fallback_contracts: Default::default(),
490 non_fallback_contracts: Default::default(),
491 functions,
492 functions_by_address: Default::default(),
493 constructors_by_address: Default::default(),
494 constructor_args_offsets: Default::default(),
495 events,
496 events_by_address: None,
497 revert_decoder: RevertDecoder::new().with_abis(tempo_abis.iter()),
499
500 signature_identifier: None,
501 verbosity: 0,
502
503 debug_identifier: None,
504
505 disable_labels: false,
506
507 chain_id: None,
508 networks: None,
509
510 opcodes: Vec::new(),
511
512 hardfork: None,
513 compact_labels: false,
514 }
515 }
516
517 pub fn clear_addresses(&mut self) {
519 self.contracts.clear();
520
521 if self.base_labels.is_empty() {
522 self.labels.clone_from(&Self::new().labels);
523 } else {
524 self.labels.clone_from(&self.base_labels);
525 }
526
527 self.receive_contracts.clear();
528 self.fallback_contracts.clear();
529 self.non_fallback_contracts.clear();
530 self.functions_by_address.clear();
531 self.events_by_address = None;
532 self.constructors_by_address.clear();
533 self.constructor_args_offsets.clear();
534
535 self.register_celo_metadata();
536 #[cfg(feature = "monad")]
537 self.register_monad_metadata();
538 #[cfg(feature = "base")]
539 self.register_base_metadata();
540 }
541
542 pub fn precompile_labels(&self) -> AddressHashMap<String> {
544 self.labels
545 .iter()
546 .filter(|(address, _)| {
547 precompiles::is_known_precompile(
548 **address,
549 self.networks,
550 self.chain_id,
551 self.hardfork,
552 )
553 })
554 .map(|(address, label)| (*address, label.clone()))
555 .collect()
556 }
557
558 pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
562 self.collect_identified_addresses(self.identify_addresses(arena, identifier), true);
563 }
564
565 pub fn identify_scoped(
569 &mut self,
570 arena: &CallTraceArena,
571 identifier: &mut impl TraceIdentifier,
572 ) {
573 self.collect_identified_addresses(self.identify_addresses(arena, identifier), false);
574 }
575
576 pub fn identify_addresses<'a>(
580 &self,
581 arena: &CallTraceArena,
582 identifier: &'a mut impl TraceIdentifier,
583 ) -> Vec<IdentifiedAddress<'a>> {
584 let nodes = arena.nodes().iter().filter(|node| {
585 if node.is_precompile()
587 || precompiles::is_known_precompile_call(
588 &node.trace,
589 self.networks,
590 self.chain_id,
591 self.hardfork,
592 )
593 {
594 return false;
595 }
596 let address = &node.trace.address;
597 !self.labels.contains_key(address) || !self.contracts.contains_key(address)
598 });
599 identifier.identify_addresses(&nodes.collect::<Vec<_>>())
600 }
601
602 pub fn push_event(&mut self, event: Event) {
604 self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
605 }
606
607 pub fn push_address_event(&mut self, address: Address, event: Event) {
609 let events = self
610 .events_by_address
611 .get_or_insert_with(Default::default)
612 .entry(address)
613 .or_default()
614 .entry(((!event.anonymous).then(|| event.selector()), indexed_inputs(&event)))
615 .or_default();
616 if !events.contains(&event) {
617 events.push(event);
618 }
619 }
620
621 pub fn push_function(&mut self, function: Function) {
623 let selector = function.selector();
624 let functions = self.functions.entry(selector).or_default();
625
626 if Self::push_function_to(functions, function) && functions.len() > 1 {
627 let function = functions.last().expect("function was just inserted");
628 let signature = function.signature();
629 trace!(target: "evm::traces", %selector, new=%signature, "duplicate function selector");
630 }
631 }
632
633 pub fn push_address_function(&mut self, address: Address, function: Function) {
635 let functions = self
636 .functions_by_address
637 .entry(address)
638 .or_default()
639 .entry(function.selector())
640 .or_default();
641 Self::push_function_to(functions, function);
642 }
643
644 fn push_function_to(functions: &mut Vec<Function>, function: Function) -> bool {
645 if functions.contains(&function) {
646 false
647 } else {
648 functions.push(function);
649 true
650 }
651 }
652
653 pub fn functions_for_selector(
657 &self,
658 address: Address,
659 selector: &Selector,
660 ) -> Option<&[Function]> {
661 if self.is_current_committee_active(address) {
662 static FUNCTIONS: OnceLock<HashMap<Selector, Vec<Function>>> = OnceLock::new();
663 if let Some(functions) = FUNCTIONS
664 .get_or_init(|| {
665 ICurrentCommittee::abi::functions()
666 .into_values()
667 .flatten()
668 .map(|function| (function.selector(), vec![function]))
669 .collect()
670 })
671 .get(selector)
672 {
673 return Some(functions);
674 }
675 }
676 if let Some(functions) =
677 self.functions_by_address.get(&address).and_then(|functions| functions.get(selector))
678 {
679 return Some(functions);
680 }
681 #[cfg(feature = "base")]
682 let base_functions = self.base_functions_for_selector(selector);
683 #[cfg(feature = "base")]
685 if let Some(functions) = base_functions
686 && functions.first().is_some_and(|function| function.name == "transferWithMemo")
687 {
688 if let Some(global) = self.functions.get(selector) {
689 let tempo_fallback = global.first().is_some_and(|function| {
690 function.name == "transferWithMemo" && function.outputs.is_empty()
691 });
692 if !tempo_fallback {
693 return Some(global);
694 }
695 if global.len() > 1 {
696 return Some(&global[1..]);
697 }
698 }
699 return Some(functions);
700 }
701 let functions = self.functions.get(selector);
702 #[cfg(feature = "base")]
703 let functions = functions.or(base_functions);
704 functions.map(Vec::as_slice)
705 }
706
707 #[cfg(feature = "base")]
708 fn is_base_context(&self) -> bool {
709 self.networks.map_or_else(
710 || self.hardfork.and_then(BaseSpecId::from_foundry_hardfork).is_some(),
711 |networks| networks.is_base(),
712 )
713 }
714
715 #[cfg(feature = "base")]
716 fn base_functions_for_selector(&self, selector: &Selector) -> Option<&'static Vec<Function>> {
717 if !self.is_base_context() {
718 return None;
719 }
720 static FUNCTIONS: OnceLock<HashMap<Selector, Vec<Function>>> = OnceLock::new();
721 FUNCTIONS
722 .get_or_init(|| {
723 base::IB20Extensions::abi::functions()
724 .into_values()
725 .flatten()
726 .map(|function| (function.selector(), vec![function]))
727 .collect()
728 })
729 .get(selector)
730 }
731
732 #[cfg(feature = "base")]
733 fn base_events(&self) -> Option<&'static BTreeMap<(B256, usize), Vec<Event>>> {
734 if !self.is_base_context() {
735 return None;
736 }
737 static EVENTS: OnceLock<BTreeMap<(B256, usize), Vec<Event>>> = OnceLock::new();
738 Some(EVENTS.get_or_init(|| {
739 base::IB20Extensions::abi::events()
740 .into_values()
741 .flatten()
742 .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
743 .collect()
744 }))
745 }
746
747 #[cfg(feature = "base")]
748 fn base_revert_decoder(&self, address: Address) -> Option<&'static RevertDecoder> {
749 let upgrade =
750 self.hardfork.and_then(BaseSpecId::from_foundry_hardfork).map(|spec| spec.upgrade())?;
751 if !self.is_base_context() || !is_base_precompile_active_at(address, upgrade) {
752 return None;
753 }
754
755 static DECODERS: OnceLock<HashMap<Address, RevertDecoder>> = OnceLock::new();
756 DECODERS
757 .get_or_init(|| {
758 HashMap::from_iter([
759 (
760 ActivationRegistryStorage::ADDRESS,
761 RevertDecoder::new().with_abi(&base::IActivationRegistry::abi::contract()),
762 ),
763 (
764 PolicyRegistryStorage::ADDRESS,
765 RevertDecoder::new().with_abi(&base::IPolicyRegistry::abi::contract()),
766 ),
767 (
768 B20FactoryStorage::ADDRESS,
769 RevertDecoder::new().with_abi(&base::IB20Factory::abi::contract()),
770 ),
771 (
772 NonceManagerStorage::ADDRESS,
773 RevertDecoder::new().with_abi(&base::INonceManager::abi::contract()),
774 ),
775 (
776 TxContextStorage::ADDRESS,
777 RevertDecoder::new().with_abi(&base::ITransactionContext::abi::contract()),
778 ),
779 ])
780 })
781 .get(&address)
782 }
783
784 #[cfg(feature = "monad")]
785 fn register_monad_metadata(&mut self) {
786 if self.networks.is_some_and(|networks| !networks.is_monad()) {
787 return;
788 }
789 let Some(hardfork) = self.hardfork.and_then(MonadHardfork::from_foundry_hardfork) else {
790 return;
791 };
792
793 self.labels
794 .entry(foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS)
795 .or_insert_with(|| "MonadVM".to_string());
796 self.register_address_abi(
797 monad_revm::staking::STAKING_ADDRESS,
798 &monad::IMonadStaking::abi::contract(),
799 );
800 self.register_address_abi(
801 monad_revm::staking::STAKING_ADDRESS,
802 &monad::IMonadStakingSyscalls::abi::contract(),
803 );
804 self.labels
805 .entry(monad_revm::staking::STAKING_ADDRESS)
806 .or_insert_with(|| "Staking".to_string());
807
808 if foundry_evm_networks::is_monad_precompile_active_at(
809 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
810 hardfork,
811 ) {
812 self.register_address_abi(
813 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
814 &monad::IReserveBalance::abi::contract(),
815 );
816 self.labels
817 .entry(monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
818 .or_insert_with(|| "ReserveBalance".to_string());
819 }
820 }
821
822 #[cfg(any(feature = "base", feature = "monad"))]
823 fn register_address_abi(&mut self, address: Address, abi: &JsonAbi) {
824 for function in abi.functions() {
825 self.push_address_function(address, function.clone());
826 }
827 for event in abi.events() {
828 self.push_address_event(address, event.clone());
829 }
830 }
831
832 fn is_current_committee_active(&self, address: Address) -> bool {
833 address == CURRENT_COMMITTEE_ADDRESS
834 && self
835 .hardfork
836 .and_then(TempoHardfork::from_foundry_hardfork)
837 .is_some_and(|hardfork| hardfork.is_t8())
838 && precompiles::is_known_precompile(
839 address,
840 self.networks,
841 self.chain_id,
842 self.hardfork,
843 )
844 }
845
846 fn select_contract_function<'a>(
852 &self,
853 functions: &'a [Function],
854 trace: &CallTrace,
855 ) -> &'a [Function] {
856 if trace.address != CHEATCODE_ADDRESS
860 && let Some(args) = suffixed_abi_args(&trace.data)
861 {
862 return functions
863 .iter()
864 .position(|func| is_abi_encoded_input(func, args))
865 .map_or(&[], |i| &functions[i..=i]);
866 }
867
868 if functions.len() > 1 {
872 for (i, func) in functions.iter().enumerate() {
873 if trace.data.len() >= SELECTOR_LEN
874 && func.abi_decode_input(&trace.data[SELECTOR_LEN..]).is_ok()
875 {
876 return &functions[i..i + 1];
877 }
878 }
879 }
880 functions
881 }
882
883 pub fn push_error(&mut self, error: Error) {
885 self.revert_decoder.push_error(error);
886 }
887
888 pub const fn without_label(&mut self, disable: bool) {
889 self.disable_labels = disable;
890 }
891
892 fn collect_identified_addresses(
893 &mut self,
894 mut addrs: Vec<IdentifiedAddress<'_>>,
895 global: bool,
896 ) {
897 addrs.sort_by_key(|identity| identity.address);
898 addrs.dedup_by_key(|identity| identity.address);
899 if addrs.is_empty() {
900 return;
901 }
902
903 trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
904 for IdentifiedAddress {
905 address,
906 label,
907 contract,
908 abi,
909 constructor_args_offset,
910 artifact_id: _,
911 } in addrs
912 {
913 let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
914
915 if let Some(contract) = contract {
916 self.contracts.entry(address).or_insert(contract);
917 }
918
919 if let Some(label) = label.filter(|s| !s.is_empty()) {
920 self.labels.entry(address).or_insert(label);
921 }
922
923 if let Some(abi) = abi {
924 self.collect_abi(&abi, Some(address), global);
925 }
926
927 if let Some(offset) = constructor_args_offset {
928 self.constructor_args_offsets.entry(address).or_insert(offset);
929 }
930 }
931 }
932
933 fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>, global: bool) {
934 let len = abi.len();
935 if len == 0 {
936 return;
937 }
938 trace!(target: "evm::traces", len, ?address, "collecting ABI");
939 for function in abi.functions() {
940 if let Some(address) = address {
941 self.push_address_function(address, function.clone());
942 }
943 if global {
944 self.push_function(function.clone());
945 }
946 }
947 if let Some(address) = address
948 && let Some(constructor) = abi.constructor()
949 {
950 self.constructors_by_address.entry(address).or_insert_with(|| constructor.clone());
951 }
952 if global {
953 for event in abi.events() {
954 self.push_event(event.clone());
955 }
956 for error in abi.errors() {
957 self.push_error(error.clone());
958 }
959 }
960 if let Some(address) = address {
961 if abi.receive.is_some() {
962 self.receive_contracts.insert(address);
963 }
964
965 if abi.fallback.is_some() {
966 self.fallback_contracts
967 .insert(address, abi.functions().map(|f| f.selector()).collect());
968 } else {
969 self.non_fallback_contracts
970 .insert(address, abi.functions().map(|f| f.selector()).collect());
971 }
972 }
973 }
974
975 pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
979 for node in traces {
980 if !self.opcodes.is_empty() {
981 for step in &mut node.trace.steps {
982 if step.decoded.is_some() {
983 continue;
984 }
985 for opcode in &self.opcodes {
986 if step.op == *opcode {
987 let res = match &step.storage_change {
988 Some(change) if step.op == OpCode::SSTORE => {
989 if let Some(had_value) = change.had_value {
990 format!(
991 "[{}] {} 0x{:x}: 0x{:x} → 0x{:x}",
992 step.gas_cost,
993 opcode,
994 change.key,
995 had_value,
996 change.value
997 )
998 } else {
999 format!(
1000 "[{}] {} 0x{:x} → (0x{:x})",
1001 step.gas_cost, opcode, change.key, change.value
1002 )
1003 }
1004 }
1005 Some(change) => format!(
1006 "[{}] {} 0x{:x} → (0x{:x})",
1007 step.gas_cost, opcode, change.key, change.value
1008 ),
1009 None => format!("[{}] {}", step.gas_cost, opcode),
1010 };
1011
1012 step.decoded = Some(Box::new(DecodedTraceStep::Line(res)));
1013 break;
1014 }
1015 }
1016 }
1017 }
1018
1019 node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await));
1020 for log in &mut node.logs {
1021 log.decoded =
1022 Some(Box::new(self.decode_event_with_address(log.address, &log.raw_log).await));
1023 }
1024
1025 if let Some(debug) = self.debug_identifier.as_ref()
1026 && let Some(identified) = self.contracts.get(&node.trace.address)
1027 {
1028 debug.identify_node_steps(node, get_contract_name(identified))
1029 }
1030 }
1031 }
1032
1033 pub async fn decode_function_signature(&self, trace: &CallTrace) -> Option<String> {
1036 if !trace.kind.is_any_create()
1037 && trace.address != DEFAULT_CREATE2_DEPLOYER
1038 && !precompiles::is_known_precompile_call(
1039 trace,
1040 self.networks,
1041 self.chain_id,
1042 self.hardfork,
1043 )
1044 && is_abi_call_data(&trace.data)
1045 && suffixed_abi_args(&trace.data).is_none()
1047 && let Some(selector) = trace.data.first_chunk().map(Selector::from)
1048 && let Some([function]) = self.functions_for_selector(trace.address, &selector)
1049 && self
1050 .fallback_contracts
1051 .get(&trace.address)
1052 .is_none_or(|selectors| selectors.contains(&selector))
1053 {
1054 return Some(function.signature());
1055 }
1056
1057 self.decode_function(trace).await.call_data.map(|data| data.signature)
1058 }
1059
1060 pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
1062 let label = if self.disable_labels {
1063 None
1064 } else if let Some(label) = self.labels.get(&trace.address) {
1065 Some(label.clone())
1066 } else if self.is_current_committee_active(trace.address) {
1067 Some("CurrentCommittee".to_string())
1068 } else {
1069 None
1070 };
1071
1072 if trace.kind.is_any_create() {
1073 return DecodedCallTrace {
1074 label,
1075 call_data: self.decode_constructor_input(trace),
1076 return_data: None,
1077 };
1078 }
1079
1080 if let Some(trace) = precompiles::decode(trace, self.networks, self.chain_id, self.hardfork)
1081 {
1082 return trace;
1083 }
1084
1085 let cdata = &trace.data;
1086 if trace.address == DEFAULT_CREATE2_DEPLOYER {
1087 return DecodedCallTrace {
1088 label,
1089 call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
1090 return_data: self.default_return_data(trace).await,
1091 };
1092 }
1093
1094 if is_abi_call_data(cdata) {
1095 let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
1096 let mut identified_functions = Vec::new();
1097 let functions = match self.functions_for_selector(trace.address, &selector) {
1098 Some(functions) => functions,
1099 None => {
1100 if let Some(identifier) = &self.signature_identifier
1101 && let Some(function) = identifier.identify_function(selector).await
1102 {
1103 identified_functions.push(function);
1104 }
1105 &identified_functions
1106 }
1107 };
1108
1109 let contract_functions = self.select_contract_function(functions, trace);
1110
1111 if !trace.kind.is_delegate()
1115 && let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address)
1116 && !contract_selectors.contains(&selector)
1117 && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address))
1118 {
1119 let return_data = if trace.success {
1120 None
1121 } else {
1122 let revert_msg =
1123 self.decode_revert_at(trace.address, &trace.output, trace.status).await;
1124
1125 if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") {
1126 Some(format!(
1127 "unrecognized function selector {} for contract {}, which has no fallback function.",
1128 selector, trace.address
1129 ))
1130 } else {
1131 Some(revert_msg)
1132 }
1133 };
1134
1135 return if let Some(func) = contract_functions.first() {
1136 DecodedCallTrace {
1137 label,
1138 call_data: Some(self.decode_function_input(trace, func)),
1139 return_data,
1140 }
1141 } else {
1142 DecodedCallTrace {
1143 label,
1144 call_data: self.fallback_call_data(trace),
1145 return_data,
1146 }
1147 };
1148 }
1149
1150 let [func, ..] = contract_functions else {
1151 return DecodedCallTrace {
1152 label,
1153 call_data: self.fallback_call_data(trace),
1154 return_data: self.default_return_data(trace).await,
1155 };
1156 };
1157
1158 let mut call_data = self.decode_function_input(trace, func);
1161 if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address)
1162 && !fallback_functions.contains(&selector)
1163 && let Some(cd) = self.fallback_call_data(trace)
1164 {
1165 call_data.signature = cd.signature;
1166 }
1167
1168 DecodedCallTrace {
1169 label,
1170 call_data: Some(call_data),
1171 return_data: self.decode_function_output(trace, contract_functions).await,
1172 }
1173 } else {
1174 if trace.address == CHEATCODE_ADDRESS
1178 && let Some(selector) = cdata.first_chunk().map(Selector::from)
1179 && let Some([func, ..]) = self.functions_for_selector(trace.address, &selector)
1180 {
1181 return DecodedCallTrace {
1182 label,
1183 call_data: Some(self.decode_function_input(trace, func)),
1184 return_data: self.default_return_data(trace).await,
1185 };
1186 }
1187 DecodedCallTrace {
1188 label,
1189 call_data: self.fallback_call_data(trace),
1190 return_data: self.default_return_data(trace).await,
1191 }
1192 }
1193 }
1194
1195 fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
1197 let mut args = None;
1198 if trace.data.len() >= SELECTOR_LEN {
1199 if trace.address == CHEATCODE_ADDRESS {
1200 if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
1202 args = Some(v);
1203 }
1204 }
1205
1206 if args.is_none()
1207 && let Ok(decoded) = func.abi_decode_input(&trace.data[SELECTOR_LEN..])
1208 {
1209 args = Some(
1210 decoded
1211 .iter()
1212 .zip(&func.inputs)
1213 .map(|(value, input)| {
1214 self.format_param_value(
1215 Some(trace.address),
1216 &func.name,
1217 &input.name,
1218 &input.ty,
1219 value,
1220 )
1221 })
1222 .collect(),
1223 );
1224 }
1225 }
1226
1227 DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
1228 }
1229
1230 fn decode_constructor_input(&self, trace: &CallTrace) -> Option<DecodedCallData> {
1232 let constructor = self.constructors_by_address.get(&trace.address)?;
1233 let offset = *self.constructor_args_offsets.get(&trace.address)?;
1234 let data = trace.data.get(offset..)?;
1235 let decoded = constructor.abi_decode_input(data).ok()?;
1236 let args = decoded
1237 .iter()
1238 .zip(&constructor.inputs)
1239 .map(|(value, input)| {
1240 self.format_param_value(
1241 Some(trace.address),
1242 "constructor",
1243 &input.name,
1244 &input.ty,
1245 value,
1246 )
1247 })
1248 .collect();
1249
1250 Some(DecodedCallData { signature: constructor_signature(constructor), args })
1251 }
1252
1253 fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
1255 match func.name.as_str() {
1256 "expectRevert" => {
1257 let decoded = match data.get(SELECTOR_LEN..) {
1258 Some(data) => func.abi_decode_input(data).ok(),
1259 None => None,
1260 };
1261 let Some(decoded) = decoded else {
1262 return Some(vec![self.revert_decoder.decode(data, None)]);
1263 };
1264 let Some(first) = decoded.first() else {
1265 return Some(vec![self.revert_decoder.decode(data, None)]);
1266 };
1267 let expected_revert = match first {
1268 DynSolValue::Bytes(bytes) => bytes.as_slice(),
1269 DynSolValue::FixedBytes(word, size) => &word[..*size],
1270 _ => return None,
1271 };
1272 Some(
1273 std::iter::once(self.revert_decoder.decode(expected_revert, None))
1274 .chain(decoded.iter().skip(1).map(|value| self.format_value(value)))
1275 .collect(),
1276 )
1277 }
1278 "addr" | "createEd25519Key" | "createWallet" | "deriveKey" | "publicKeyEd25519" |
1279 "publicKeyP256" | "rememberKey" | "rememberKeys" => {
1280 Some(vec!["<pk>".to_string()])
1282 }
1283 "broadcast" | "startBroadcast" => {
1284 (!func.inputs.is_empty() && func.inputs[0].ty == "uint256").then(|| vec!["<pk>".to_string()])
1287 }
1288 "getNonce" => {
1289 (!func.inputs.is_empty() && func.inputs[0].ty == "tuple").then(|| vec!["<pk>".to_string()])
1292 }
1293 "sign" | "signCompact" | "signP256" | "signKeychain" | "signKeychainAdmin" => {
1294 let Ok(mut decoded) = func.abi_decode_input(&data[SELECTOR_LEN..]) else {
1297 return Some(vec!["<redacted>".to_string()]);
1298 };
1299
1300 if !decoded.is_empty() &&
1304 (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
1305 {
1306 decoded[0] = DynSolValue::String("<pk>".to_string());
1307 }
1308
1309 Some(decoded.iter().map(|value| self.format_value(value)).collect())
1310 }
1311 "signWithNonceUnsafe" => {
1312 let Ok(mut decoded) = func.abi_decode_input(&data[SELECTOR_LEN..]) else {
1313 return Some(vec!["<redacted>".to_string()]);
1314 };
1315 decoded[0] = DynSolValue::String("<pk>".to_string());
1320 decoded[2] = DynSolValue::String("<nonce>".to_string());
1321 Some(decoded.iter().map(|value| self.format_value(value)).collect())
1322 }
1323 "signEd25519" => {
1324 let Ok(mut decoded) = func.abi_decode_input(&data[SELECTOR_LEN..]) else {
1325 return Some(vec!["<redacted>".to_string()]);
1326 };
1327 decoded[2] = DynSolValue::String("<pk>".to_string());
1330 Some(decoded.iter().map(|value| self.format_value(value)).collect())
1331 }
1332 "signDelegation" | "signAndAttachDelegation" => {
1333 let Ok(mut decoded) = func.abi_decode_input(&data[SELECTOR_LEN..]) else {
1334 return Some(vec!["<redacted>".to_string()]);
1335 };
1336 decoded[1] = DynSolValue::String("<pk>".to_string());
1340 Some(decoded.iter().map(format_token).collect())
1341 }
1342 "parseJson" |
1343 "parseJsonUint" |
1344 "parseJsonUintArray" |
1345 "parseJsonInt" |
1346 "parseJsonIntArray" |
1347 "parseJsonString" |
1348 "parseJsonStringArray" |
1349 "parseJsonAddress" |
1350 "parseJsonAddressArray" |
1351 "parseJsonBool" |
1352 "parseJsonBoolArray" |
1353 "parseJsonBytes" |
1354 "parseJsonBytesArray" |
1355 "parseJsonBytes32" |
1356 "parseJsonBytes32Array" |
1357 "writeJson" |
1358 "keyExists" |
1360 "keyExistsJson" |
1361 "serializeBool" |
1362 "serializeUint" |
1363 "serializeUintToHex" |
1364 "serializeInt" |
1365 "serializeAddress" |
1366 "serializeBytes32" |
1367 "serializeString" |
1368 "serializeBytes" => {
1369 if self.verbosity >= 5 {
1370 None
1371 } else {
1372 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
1373 let token = if func.name.as_str() == "parseJson" ||
1374 func.name.as_str() == "keyExists" ||
1376 func.name.as_str() == "keyExistsJson"
1377 {
1378 "<JSON file>"
1379 } else {
1380 "<stringified JSON>"
1381 };
1382 decoded[0] = DynSolValue::String(token.to_string());
1383 Some(decoded.iter().map(format_token).collect())
1384 }
1385 }
1386 s if s.contains("Toml") => {
1387 if self.verbosity >= 5 {
1388 None
1389 } else {
1390 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
1391 let token = if func.name.as_str() == "parseToml" ||
1392 func.name.as_str() == "keyExistsToml"
1393 {
1394 "<TOML file>"
1395 } else {
1396 "<stringified TOML>"
1397 };
1398 decoded[0] = DynSolValue::String(token.to_string());
1399 Some(decoded.iter().map(format_token).collect())
1400 }
1401 }
1402 "createFork" |
1403 "createSelectFork" |
1404 "rpc" => {
1405 let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
1406
1407 if !decoded.is_empty() && func.inputs[0].ty == "string" {
1409 let url_or_alias = decoded[0].as_str().unwrap_or_default();
1410
1411 if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
1412 decoded[0] = DynSolValue::String("<rpc url>".to_string());
1413 }
1414 } else {
1415 return None;
1416 }
1417
1418 Some(decoded.iter().map(format_token).collect())
1419 }
1420 _ => None,
1421 }
1422 }
1423
1424 async fn decode_function_output(
1426 &self,
1427 trace: &CallTrace,
1428 funcs: &[Function],
1429 ) -> Option<String> {
1430 if !trace.success {
1431 return self.default_return_data(trace).await;
1432 }
1433
1434 if trace.address == CHEATCODE_ADDRESS
1435 && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
1436 {
1437 return Some(decoded);
1438 }
1439
1440 if let Some(values) =
1441 funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok())
1442 {
1443 if values.is_empty() {
1446 return None;
1447 }
1448
1449 return Some(
1450 values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
1451 );
1452 }
1453
1454 None
1455 }
1456
1457 fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
1459 match func.name.as_str() {
1460 s if s.starts_with("env") => Some("<env var value>"),
1461 "createEd25519Key" | "createWallet" | "deriveKey" => Some("<pk>"),
1462 "promptSecret" | "promptSecretUint" => Some("<secret>"),
1463 "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
1464 "readFile" if self.verbosity < 5 => Some("<file>"),
1465 "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
1466 _ => None,
1467 }
1468 .map(Into::into)
1469 }
1470
1471 #[track_caller]
1472 fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
1473 let cdata = &trace.data;
1474 let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
1475 "receive()"
1476 } else if self.fallback_contracts.contains_key(&trace.address) {
1477 "fallback()"
1478 } else {
1479 return None;
1480 }
1481 .to_string();
1482 let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
1483 Some(DecodedCallData { signature, args })
1484 }
1485
1486 async fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
1488 if trace.status.is_none_or(|s| s.is_ok()) || trace.success {
1493 return None;
1494 }
1495 Some(self.decode_revert_at(trace.address, &trace.output, trace.status).await)
1496 }
1497
1498 async fn decode_revert(&self, output: &[u8], status: Option<InstructionResult>) -> String {
1505 if let Some(reason) = self.revert_decoder.maybe_decode_known(output) {
1506 return reason;
1507 }
1508 if let Some(identifier) = &self.signature_identifier
1509 && let Some((selector, data)) = output.split_first_chunk::<SELECTOR_LEN>()
1510 && let Some(error) = identifier.identify_error(Selector::from(*selector)).await
1511 && let Ok(decoded) = error.abi_decode_input(data)
1512 {
1513 return format!("{}({})", error.name, decoded.iter().map(format_token).format(", "));
1514 }
1515 self.revert_decoder.decode(output, status)
1516 }
1517
1518 async fn decode_revert_at(
1519 &self,
1520 address: Address,
1521 output: &[u8],
1522 status: Option<InstructionResult>,
1523 ) -> String {
1524 #[cfg(feature = "base")]
1525 if let Some(reason) =
1526 self.base_revert_decoder(address).and_then(|decoder| decoder.maybe_decode_known(output))
1527 {
1528 return reason;
1529 }
1530 if self.is_current_committee_active(address) {
1531 static DECODER: OnceLock<RevertDecoder> = OnceLock::new();
1532 let decoder = DECODER
1533 .get_or_init(|| RevertDecoder::new().with_abi(&ICurrentCommittee::abi::contract()));
1534 if let Some(reason) = decoder.maybe_decode_known(output) {
1535 return reason;
1536 }
1537 }
1538 self.decode_revert(output, status).await
1539 }
1540
1541 pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
1543 self.decode_event_inner(None, log, false).await.into_call_log()
1544 }
1545
1546 pub async fn decode_event_with_address(
1548 &self,
1549 address: Address,
1550 log: &LogData,
1551 ) -> DecodedCallLog {
1552 self.decode_event_inner(Some(address), log, false).await.into_call_log()
1553 }
1554
1555 pub async fn decode_event_with_address_signature(
1557 &self,
1558 address: Address,
1559 log: &LogData,
1560 ) -> DecodedEvent {
1561 self.decode_event_inner(Some(address), log, true).await
1562 }
1563
1564 async fn decode_event_inner(
1565 &self,
1566 address: Option<Address>,
1567 log: &LogData,
1568 canonical_signature: bool,
1569 ) -> DecodedEvent {
1570 let key = log.topics().first().map(|&topic| (topic, log.topics().len() - 1));
1571 let events = address.and_then(|address| self.events_by_address.as_deref()?.get(&address));
1572 let address_events = key.and_then(|(topic, count)| events?.get(&(Some(topic), count)));
1573 let global_events = key.and_then(|key| self.events.get(&key));
1574 #[cfg(feature = "base")]
1575 let base_events = key.and_then(|key| self.base_events()?.get(&key));
1576 #[cfg(not(feature = "base"))]
1577 let base_events: Option<&Vec<Event>> = None;
1578 let fallback_events = global_events.or(base_events);
1579 let regular_events = address_events.or(fallback_events);
1580 let anonymous_events = events.and_then(|events| events.get(&(None, log.topics().len())));
1581
1582 let decoded = if canonical_signature && address_events.is_none() {
1583 let mut events = fallback_events
1586 .into_iter()
1587 .flatten()
1588 .flat_map(|event| indexed_event_candidates(event.clone(), log))
1589 .collect::<Vec<_>>();
1590 events.extend(anonymous_events.into_iter().flatten().cloned());
1591 self.decode_unique_event_candidates(address, log, &events, true)
1592 } else if canonical_signature || anonymous_events.is_some() {
1593 self.decode_unique_event_candidates(
1594 address,
1595 log,
1596 regular_events.into_iter().flatten().chain(anonymous_events.into_iter().flatten()),
1597 canonical_signature,
1598 )
1599 } else {
1600 self.decode_event_candidates(address, log, regular_events.into_iter().flatten(), false)
1601 };
1602 if let Some(decoded) = decoded {
1603 return decoded;
1604 }
1605
1606 if regular_events.is_some() || anonymous_events.is_some() {
1607 return DecodedEvent::default();
1608 }
1609
1610 if let Some(&topic) = log.topics().first()
1611 && let Some(identifier) = &self.signature_identifier
1612 && let Some(event) = identifier.identify_event(topic).await
1613 {
1614 if !canonical_signature {
1615 let event = get_indexed_event(event, log);
1616 return self
1617 .decode_event_candidates(address, log, [&event], false)
1618 .unwrap_or_default();
1619 }
1620 let mut decoded = indexed_event_candidates(event, log)
1621 .filter_map(|event| self.decode_event_candidates(address, log, [&event], true));
1622 let event = decoded.next();
1623 if event.is_some() && decoded.next().is_some() {
1624 return DecodedEvent::default();
1625 }
1626 if let Some(decoded) = event {
1627 return decoded;
1628 }
1629 }
1630
1631 DecodedEvent::default()
1632 }
1633
1634 fn decode_event_candidates<'a>(
1635 &self,
1636 address: Option<Address>,
1637 log: &LogData,
1638 events: impl IntoIterator<Item = &'a Event>,
1639 canonical_signature: bool,
1640 ) -> Option<DecodedEvent> {
1641 for event in events {
1642 if let Ok(decoded) = event.decode_log(log) {
1643 let params = reconstruct_event_params(event, &decoded);
1644 let params = params
1645 .into_iter()
1646 .zip(event.inputs.iter())
1647 .map(|(param, input)| {
1648 let name = input.name.clone();
1650 let display = self.format_param_value(
1651 address,
1652 &event.name,
1653 &input.name,
1654 &input.ty,
1655 ¶m,
1656 );
1657 (name, display, param)
1658 })
1659 .collect();
1660 return Some(DecodedEvent {
1661 name: Some(if canonical_signature {
1662 event.signature()
1663 } else {
1664 event.name.clone()
1665 }),
1666 params: Some(params),
1667 });
1668 }
1669 }
1670 None
1671 }
1672
1673 fn decode_unique_event_candidates<'a>(
1674 &self,
1675 address: Option<Address>,
1676 log: &LogData,
1677 events: impl IntoIterator<Item = &'a Event>,
1678 canonical_signature: bool,
1679 ) -> Option<DecodedEvent> {
1680 let mut decoded = events.into_iter().filter_map(|event| {
1681 self.decode_event_candidates(address, log, [event], canonical_signature)
1682 });
1683 let event = decoded.next()?;
1684 decoded.next().is_none().then_some(event)
1685 }
1686
1687 pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
1689 let Some(identifier) = &self.signature_identifier else { return };
1690 let events = nodes
1691 .iter()
1692 .flat_map(|node| {
1693 node.logs
1694 .iter()
1695 .map(|log| log.raw_log.topics())
1696 .filter(|&topics| {
1697 if let Some(&first) = topics.first()
1698 && self.events.contains_key(&(first, topics.len() - 1))
1699 {
1700 return false;
1701 }
1702 true
1703 })
1704 .filter_map(|topics| topics.first())
1705 })
1706 .copied();
1707 let functions = nodes
1708 .iter()
1709 .filter(|&n| {
1710 if n.trace.address == DEFAULT_CREATE2_DEPLOYER
1712 || n.is_precompile()
1713 || precompiles::is_known_precompile_call(
1714 &n.trace,
1715 self.networks,
1716 self.chain_id,
1717 self.hardfork,
1718 )
1719 {
1720 return false;
1721 }
1722 if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
1724 return false;
1725 }
1726 true
1727 })
1728 .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
1729 .filter(|selector| !self.functions.contains_key(selector));
1730 let errors = nodes
1731 .iter()
1732 .filter(|&n| {
1733 n.trace.status.is_some_and(|s| !s.is_ok())
1735 && !n.trace.success
1736 && self.revert_decoder.maybe_decode_known(&n.trace.output).is_none()
1737 })
1738 .filter_map(|n| n.trace.output.first_chunk().map(Selector::from));
1739 let selectors = events
1740 .map(SelectorKind::Event)
1741 .chain(functions.map(SelectorKind::Function))
1742 .chain(errors.map(SelectorKind::Error))
1743 .unique()
1744 .collect::<Vec<_>>();
1745 let _ = identifier.identify(&selectors).await;
1746 }
1747
1748 fn format_value(&self, value: &DynSolValue) -> String {
1750 if let DynSolValue::Address(addr) = value
1751 && !self.disable_labels
1752 && let Some(label) = self.labels.get(addr)
1753 {
1754 if self.compact_labels {
1755 return label.clone();
1756 }
1757 return format!("{label}: [{addr}]");
1758 }
1759 format_token(value)
1760 }
1761
1762 fn format_param_value(
1763 &self,
1764 address: Option<Address>,
1765 context_name: &str,
1766 input_name: &str,
1767 input_ty: &str,
1768 value: &DynSolValue,
1769 ) -> String {
1770 self.format_claim_receipt_bytes(address, context_name, input_name, input_ty, value)
1771 .map(|value| self.format_value(&value))
1772 .unwrap_or_else(|| self.format_value(value))
1773 }
1774
1775 fn format_claim_receipt_bytes(
1776 &self,
1777 address: Option<Address>,
1778 context_name: &str,
1779 input_name: &str,
1780 input_ty: &str,
1781 value: &DynSolValue,
1782 ) -> Option<DynSolValue> {
1783 if !matches!(address, Some(RECEIVE_POLICY_GUARD_ADDRESS | TIP403_REGISTRY_ADDRESS)) {
1784 return None;
1785 }
1786 if input_name != "receipt" || input_ty != "bytes" {
1787 return None;
1788 }
1789 if !matches!(context_name, "balanceOf" | "claim" | "burnBlockedReceipt" | "TransferBlocked")
1790 {
1791 return None;
1792 }
1793 let DynSolValue::Bytes(bytes) = value else { return None };
1794 let decoded = IReceivePolicyGuard::ClaimReceiptV1::abi_decode(bytes).ok()?;
1795
1796 Some(DynSolValue::CustomStruct {
1797 name: "ClaimReceiptV1".to_string(),
1798 prop_names: vec![
1799 "version".to_string(),
1800 "token".to_string(),
1801 "recoveryAuthority".to_string(),
1802 "originator".to_string(),
1803 "recipient".to_string(),
1804 "blockedAt".to_string(),
1805 "blockedNonce".to_string(),
1806 "blockedReason".to_string(),
1807 "kind".to_string(),
1808 "memo".to_string(),
1809 ],
1810 tuple: vec![
1811 DynSolValue::Uint(U256::from(decoded.version), 8),
1812 DynSolValue::Address(decoded.token),
1813 DynSolValue::Address(decoded.recoveryAuthority),
1814 DynSolValue::Address(decoded.originator),
1815 DynSolValue::Address(decoded.recipient),
1816 DynSolValue::Uint(U256::from(decoded.blockedAt), 64),
1817 DynSolValue::Uint(U256::from(decoded.blockedNonce), 64),
1818 DynSolValue::Uint(U256::from(decoded.blockedReason), 8),
1819 DynSolValue::Uint(U256::from(decoded.kind as u8), 8),
1820 DynSolValue::FixedBytes(decoded.memo, 32),
1821 ],
1822 })
1823 }
1824}
1825
1826fn is_abi_call_data(data: &[u8]) -> bool {
1831 let data = erc8021::strip_suffix(data);
1832 match data.len().cmp(&SELECTOR_LEN) {
1833 std::cmp::Ordering::Less => false,
1834 std::cmp::Ordering::Equal => true,
1835 std::cmp::Ordering::Greater => {
1836 is_abi_data(&data[SELECTOR_LEN..]) || suffixed_abi_args(data).is_some()
1837 }
1838 }
1839}
1840
1841fn is_abi_data(data: &[u8]) -> bool {
1845 let rem = data.len() % 32;
1846 if rem == 0 || data.is_empty() {
1847 return true;
1848 }
1849 data[data.len() - rem..].iter().all(|byte| *byte == 0)
1851}
1852
1853fn suffixed_abi_args(data: &[u8]) -> Option<&[u8]> {
1860 let args = erc8021::strip_suffix(data).get(SELECTOR_LEN..)?;
1861 let (args, suffix) = args.split_at(args.len() - args.len() % 32);
1862 (args.starts_with(&[0; 12]) && suffix.iter().any(|byte| *byte != 0)).then_some(args)
1863}
1864
1865fn is_abi_encoded_input(func: &Function, args: &[u8]) -> bool {
1867 func.abi_decode_input(args)
1868 .and_then(|values| func.abi_encode_input_raw(&values))
1869 .is_ok_and(|encoded| encoded == args)
1870}
1871
1872fn indexed_inputs(event: &Event) -> usize {
1873 event.inputs.iter().filter(|param| param.indexed).count()
1874}
1875
1876fn indexed_event_candidates(mut event: Event, log: &LogData) -> impl Iterator<Item = Event> {
1877 for (index, input) in event.inputs.iter_mut().enumerate() {
1878 if input.name.is_empty() {
1879 input.name = format!("param{index}");
1880 }
1881 input.indexed = false;
1882 }
1883 (0..event.inputs.len()).combinations(log.topics().len() - 1).map(move |indexed| {
1884 let mut event = event.clone();
1885 for index in indexed {
1886 event.inputs[index].indexed = true;
1887 }
1888 event
1889 })
1890}
1891
1892fn constructor_signature(constructor: &Constructor) -> String {
1893 format!(
1894 "constructor({})",
1895 constructor.inputs.iter().map(|input| input.selector_type()).format(",")
1896 )
1897}
1898
1899#[cfg(test)]
1900mod tests {
1901 use super::*;
1902 use crate::CallKind;
1903 use alloy_primitives::{address, aliases::U96, bytes, hex};
1904 use alloy_sol_types::{SolCall, SolError, SolEvent};
1905 use foundry_evm_core::{abi::Vm, precompiles::P256_VERIFY};
1906 use std::borrow::Cow;
1907 use tempo_precompiles::{
1908 ACCOUNT_KEYCHAIN_ADDRESS, SIGNATURE_VERIFIER_ADDRESS, STORAGE_CREDITS_ADDRESS,
1909 TIP_FEE_MANAGER_ADDRESS, TIP20_CHANNEL_RESERVE_ADDRESS, TIP20_FACTORY_ADDRESS,
1910 VALIDATOR_CONFIG_V2_ADDRESS,
1911 };
1912
1913 #[cfg(feature = "base")]
1914 use foundry_evm_hardforks::BaseUpgrade;
1915
1916 #[cfg(feature = "monad")]
1917 use monad_revm::{
1918 reserve_balance::interface::IReserveBalance::dippedIntoReserveCall,
1919 staking::interface::IMonadStaking::getEpochCall,
1920 };
1921
1922 #[cfg(feature = "monad")]
1923 fn function_abi_items(functions: impl IntoIterator<Item = Function>) -> Vec<(String, String)> {
1924 let mut items = functions
1925 .into_iter()
1926 .map(|function| (function.selector().to_string(), function.signature()))
1927 .collect::<Vec<_>>();
1928 items.sort();
1929 items
1930 }
1931
1932 #[cfg(feature = "monad")]
1933 fn event_abi_items(events: impl IntoIterator<Item = Event>) -> Vec<(String, usize, String)> {
1934 let mut items = events
1935 .into_iter()
1936 .map(|event| (event.selector().to_string(), indexed_inputs(&event), event.signature()))
1937 .collect::<Vec<_>>();
1938 items.sort();
1939 items
1940 }
1941
1942 #[cfg(feature = "monad")]
1943 fn typed_call_abi_item<C: SolCall>() -> (String, String) {
1944 (Selector::from(C::SELECTOR).to_string(), C::SIGNATURE.to_string())
1945 }
1946
1947 #[cfg(feature = "monad")]
1948 fn typed_event_abi_item<E: SolEvent>() -> (String, usize, String) {
1949 let signature_topics = usize::from(!E::ANONYMOUS);
1950 let indexed_inputs = <E::TopicList as alloy_sol_types::TopicList>::COUNT - signature_topics;
1951 (E::SIGNATURE_HASH.to_string(), indexed_inputs, E::SIGNATURE.to_string())
1952 }
1953
1954 #[cfg(feature = "monad")]
1955 fn monad_decoder(hardfork: MonadHardfork) -> CallTraceDecoder {
1956 CallTraceDecoderBuilder::new()
1957 .with_execution_network(NetworkVariant::Monad)
1958 .with_chain_id(Some(143))
1959 .with_hardfork(Some(hardfork.into()))
1960 .build()
1961 }
1962
1963 #[test]
1964 #[cfg(feature = "monad")]
1965 fn test_monad_decoder_abis_match_monad_revm() {
1966 use monad_revm::{
1967 reserve_balance::interface::IReserveBalance as RevmReserveBalance,
1968 staking::interface::IMonadStaking as RevmMonadStaking,
1969 };
1970
1971 let local_staking_functions = monad::IMonadStaking::abi::functions()
1972 .into_values()
1973 .chain(monad::IMonadStakingSyscalls::abi::functions().into_values())
1974 .flatten();
1975 let mut revm_staking_functions = vec![
1976 typed_call_abi_item::<RevmMonadStaking::addValidatorCall>(),
1977 typed_call_abi_item::<RevmMonadStaking::delegateCall>(),
1978 typed_call_abi_item::<RevmMonadStaking::undelegateCall>(),
1979 typed_call_abi_item::<RevmMonadStaking::withdrawCall>(),
1980 typed_call_abi_item::<RevmMonadStaking::compoundCall>(),
1981 typed_call_abi_item::<RevmMonadStaking::claimRewardsCall>(),
1982 typed_call_abi_item::<RevmMonadStaking::changeCommissionCall>(),
1983 typed_call_abi_item::<RevmMonadStaking::externalRewardCall>(),
1984 typed_call_abi_item::<RevmMonadStaking::getEpochCall>(),
1985 typed_call_abi_item::<RevmMonadStaking::getProposerValIdCall>(),
1986 typed_call_abi_item::<RevmMonadStaking::getValidatorCall>(),
1987 typed_call_abi_item::<RevmMonadStaking::getDelegatorCall>(),
1988 typed_call_abi_item::<RevmMonadStaking::getWithdrawalRequestCall>(),
1989 typed_call_abi_item::<RevmMonadStaking::getConsensusValidatorSetCall>(),
1990 typed_call_abi_item::<RevmMonadStaking::getSnapshotValidatorSetCall>(),
1991 typed_call_abi_item::<RevmMonadStaking::getExecutionValidatorSetCall>(),
1992 typed_call_abi_item::<RevmMonadStaking::getDelegationsCall>(),
1993 typed_call_abi_item::<RevmMonadStaking::getDelegatorsCall>(),
1994 typed_call_abi_item::<RevmMonadStaking::syscallOnEpochChangeCall>(),
1995 typed_call_abi_item::<RevmMonadStaking::syscallRewardCall>(),
1996 typed_call_abi_item::<RevmMonadStaking::syscallSnapshotCall>(),
1997 ];
1998 revm_staking_functions.sort();
1999 assert_eq!(
2000 function_abi_items(local_staking_functions),
2001 revm_staking_functions,
2002 "Monad staking function selectors drifted from monad_revm",
2003 );
2004
2005 let mut revm_staking_events = vec![
2006 typed_event_abi_item::<RevmMonadStaking::ClaimRewards>(),
2007 typed_event_abi_item::<RevmMonadStaking::CommissionChanged>(),
2008 typed_event_abi_item::<RevmMonadStaking::Delegate>(),
2009 typed_event_abi_item::<RevmMonadStaking::EpochChanged>(),
2010 typed_event_abi_item::<RevmMonadStaking::Undelegate>(),
2011 typed_event_abi_item::<RevmMonadStaking::ValidatorCreated>(),
2012 typed_event_abi_item::<RevmMonadStaking::ValidatorRewarded>(),
2013 typed_event_abi_item::<RevmMonadStaking::ValidatorStatusChanged>(),
2014 typed_event_abi_item::<RevmMonadStaking::Withdraw>(),
2015 ];
2016 revm_staking_events.sort();
2017 assert_eq!(
2018 event_abi_items(monad::IMonadStaking::abi::events().into_values().flatten()),
2019 revm_staking_events,
2020 "Monad staking event selectors drifted from monad_revm",
2021 );
2022
2023 assert_eq!(
2024 function_abi_items(monad::IReserveBalance::abi::functions().into_values().flatten()),
2025 vec![typed_call_abi_item::<RevmReserveBalance::dippedIntoReserveCall>()],
2026 "Monad reserve-balance function selectors drifted from monad_revm",
2027 );
2028
2029 assert_eq!(
2030 event_abi_items(monad::IReserveBalance::abi::events().into_values().flatten()),
2031 Vec::<(String, usize, String)>::new(),
2032 "Monad reserve-balance event selectors drifted from monad_revm",
2033 );
2034 }
2035
2036 #[tokio::test]
2037 async fn function_signature_matches_full_decoding() {
2038 let address = Address::repeat_byte(0x11);
2039 let fallback = Address::repeat_byte(0x22);
2040 let no_fallback = Address::repeat_byte(0x33);
2041 let scoped = Address::repeat_byte(0x44);
2042 let known_fallback = Address::repeat_byte(0x55);
2043 let mut decoder = CallTraceDecoder::new().clone();
2044 let function = Function::parse("roundTrip(uint256[]) returns (uint256[])").unwrap();
2045 let data = function
2046 .abi_encode_input(&[DynSolValue::Array(vec![DynSolValue::Uint(U256::MAX, 256)])])
2047 .unwrap();
2048 decoder.push_function(function.clone());
2049 decoder.push_address_function(scoped, function.clone());
2050 decoder.fallback_contracts.insert(fallback, HashSet::default());
2051 decoder
2052 .fallback_contracts
2053 .insert(known_fallback, HashSet::from_iter([function.selector()]));
2054 decoder.receive_contracts.insert(fallback);
2055 decoder.non_fallback_contracts.insert(no_fallback, HashSet::default());
2056 decoder
2057 .constructors_by_address
2058 .insert(address, Constructor::parse("constructor()").unwrap());
2059 decoder.constructor_args_offsets.insert(address, 0);
2060
2061 let transfer = Function::parse("transferFrom(address,address,uint256)").unwrap();
2063 let collision = Function::parse("gasprice_bit_ether(int128)").unwrap();
2064 assert_eq!(transfer.selector(), collision.selector());
2065 let collision_data =
2066 [collision.selector().as_slice(), B256::with_last_byte(1).as_slice()].concat();
2067 decoder.push_function(transfer.clone());
2068 decoder.push_function(collision);
2069 decoder.push_address_function(scoped, transfer);
2070
2071 for address in [
2072 address,
2073 fallback,
2074 no_fallback,
2075 scoped,
2076 known_fallback,
2077 IDENTITY,
2078 DEFAULT_CREATE2_DEPLOYER,
2079 ] {
2080 for kind in [CallKind::Call, CallKind::DelegateCall, CallKind::Create] {
2081 for success in [true, false] {
2082 for data in [
2083 vec![],
2084 vec![1, 2, 3],
2085 vec![0xff; SELECTOR_LEN],
2086 function.selector().to_vec(),
2087 [function.selector().as_slice(), &[0xff; 32]].concat(),
2088 data.clone(),
2089 collision_data.clone(),
2090 ] {
2091 let trace = CallTrace {
2092 address,
2093 kind,
2094 success,
2095 status: (!success).then_some(InstructionResult::Revert),
2096 data: data.into(),
2097 output: vec![0xff; 64].into(),
2098 ..Default::default()
2099 };
2100 assert_eq!(
2101 decoder.decode_function_signature(&trace).await,
2102 decoder
2103 .decode_function(&trace)
2104 .await
2105 .call_data
2106 .map(|data| data.signature),
2107 "{address} {kind:?} success={success} calldata={}",
2108 trace.data,
2109 );
2110 }
2111 }
2112 }
2113 }
2114 }
2115
2116 #[test]
2117 fn test_selector_collision_resolution() {
2118 use alloy_json_abi::Function;
2119 use alloy_primitives::Address;
2120
2121 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
2123 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
2124
2125 assert_eq!(func1.selector(), func2.selector());
2127
2128 let functions = vec![func1, func2];
2129
2130 let trace = CallTrace {
2132 address: Address::repeat_byte(0x12),
2133 data: bytes!(
2134 "23b872dd000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000064"
2135 ),
2136 ..Default::default()
2137 };
2138
2139 let decoder = CallTraceDecoder::new();
2140 let result = decoder.select_contract_function(&functions, &trace);
2141
2142 assert_eq!(result.len(), 1);
2144 assert_eq!(result[0].signature(), "transferFrom(address,address,uint256)");
2145 }
2146
2147 #[test]
2148 fn test_selector_collision_resolution_second_function() {
2149 use alloy_json_abi::Function;
2150 use alloy_primitives::Address;
2151
2152 let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
2154 let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
2155
2156 let functions = vec![func1, func2];
2157
2158 let trace = CallTrace {
2160 address: Address::repeat_byte(0x12),
2161 data: bytes!(
2162 "23b872dd0000000000000000000000000000000000000000000000000000000000000064"
2163 ),
2164 ..Default::default()
2165 };
2166
2167 let decoder = CallTraceDecoder::new();
2168 let result = decoder.select_contract_function(&functions, &trace);
2169
2170 assert_eq!(result.len(), 1);
2172 assert_eq!(result[0].signature(), "gasprice_bit_ether(int128)");
2173 }
2174
2175 #[test]
2176 fn test_is_abi_call_data() {
2177 let call_data = |args: &[&[u8]]| [&[0x07, 0xed, 0x23, 0x79][..], &args.concat()].concat();
2178 let address = Address::repeat_byte(0x11);
2179 let word = address.into_word();
2180 let suffix = hex!("2a6f45f2");
2181
2182 assert!(is_abi_call_data(&call_data(&[&word[..]])));
2183 assert!(is_abi_call_data(&call_data(&[&word[..], &[0; 4]])));
2184 assert!(is_abi_call_data(&call_data(&[&word[..], &suffix])));
2185 assert!(!is_abi_call_data(&call_data(&[&address[..], &word[..]])));
2187 assert!(!is_abi_call_data(&call_data(&[&suffix])));
2188 let erc8021 = hex!("a161776a6364705f666163696c31000e0280218021802180218021802180218021");
2190 assert!(is_abi_call_data(&call_data(&[&B256::repeat_byte(0x11)[..], &erc8021])));
2191 }
2192
2193 #[tokio::test]
2194 async fn test_decode_call_data_with_suffix() {
2195 let abi = JsonAbi::parse([
2196 "function execute(bytes commands, bytes[] inputs, uint256 deadline)",
2197 "function chargeFee()",
2198 ])
2199 .unwrap();
2200 let identifier = SignaturesIdentifier::new_offline_with_abis([&abi]).unwrap();
2201 let mut decoder =
2202 CallTraceDecoderBuilder::new().with_signature_identifier(identifier).build();
2203 let tag = hex!("756e6978000001a0ee8a61e9800089610000006d0100");
2204 let trace = |data: &[&[u8]]| CallTrace {
2205 address: Address::repeat_byte(0x12),
2206 data: data.concat().into(),
2207 success: true,
2208 ..Default::default()
2209 };
2210
2211 let execute = abi.function("execute").unwrap().first().unwrap();
2212 let data = execute
2213 .abi_encode_input(&[
2214 DynSolValue::Bytes(vec![0x0b]),
2215 DynSolValue::Array(vec![DynSolValue::Bytes(vec![0x01; 3])]),
2216 DynSolValue::Uint(U256::ONE, 256),
2217 ])
2218 .unwrap();
2219 let call_data = decoder.decode_function(&trace(&[&data, &tag])).await.call_data.unwrap();
2220 assert_eq!(call_data.signature, "execute(bytes,bytes[],uint256)");
2221 assert_eq!(call_data.args, ["0x0b", "[0x010101]", "1"]);
2222
2223 let erc8021 = hex!(
2225 "72616e67655f7661756c742c63656c6f5f6533386364643332313061361d0080218021802180218021802180218021"
2226 );
2227 let decoded = decoder.decode_function(&trace(&[&data, &tag, &erc8021])).await;
2228 assert_eq!(decoded.call_data.unwrap().args, call_data.args);
2229
2230 let charge_fee = abi.function("chargeFee").unwrap().first().unwrap();
2233 let word = Address::repeat_byte(0x11).into_word();
2234 let packed = trace(&[&charge_fee.selector()[..], &word[..], &tag]);
2235 assert!(decoder.decode_function(&packed).await.call_data.is_none());
2236 decoder.non_fallback_contracts.insert(packed.address, HashSet::default());
2237 assert!(decoder.decode_function(&packed).await.call_data.is_none());
2238 }
2239
2240 #[cfg(feature = "base")]
2241 #[tokio::test]
2242 async fn base_fallback_does_not_shadow_ethereum_abi() {
2243 let abi = JsonAbi::parse(["function pause(uint8[] features) returns (bool)"]).unwrap();
2244 let function = abi.functions().next().unwrap();
2245 let trace = CallTrace {
2246 address: Address::repeat_byte(0x12),
2247 data: function
2248 .abi_encode_input(&[DynSolValue::Array(vec![DynSolValue::Uint(U256::ZERO, 8)])])
2249 .unwrap()
2250 .into(),
2251 output: function.abi_encode_output(&[DynSolValue::Bool(true)]).unwrap().into(),
2252 success: true,
2253 ..Default::default()
2254 };
2255
2256 let ethereum = CallTraceDecoderBuilder::new()
2257 .with_execution_network(NetworkVariant::Ethereum)
2258 .with_abi(&abi)
2259 .build();
2260 let decoded = ethereum.decode_function(&trace).await;
2261 assert_eq!(decoded.call_data.unwrap().signature, "pause(uint8[])");
2262 assert_eq!(decoded.return_data.as_deref(), Some("true"));
2263
2264 let base =
2265 CallTraceDecoderBuilder::new().with_execution_network(NetworkVariant::Base).build();
2266 let decoded = base.decode_function(&trace).await;
2267 assert_eq!(decoded.call_data.unwrap().signature, "pause(uint8[])");
2268 assert_eq!(decoded.return_data, None);
2269 }
2270
2271 #[tokio::test]
2272 async fn identified_event_does_not_shadow_builtin_metadata() {
2273 let address = Address::repeat_byte(0x12);
2274 let mut decoder = CallTraceDecoder::new().clone();
2275 let abi = JsonAbi::parse(["event log_string(string)"]).unwrap();
2276 decoder.collect_abi(&abi, Some(address), true);
2277
2278 let event = Event::parse("event log_string(string val)").unwrap();
2279 let log = LogData::new_unchecked(
2280 vec![event.selector()],
2281 ("script ran".to_string(),).abi_encode().into(),
2282 );
2283 let decoded = decoder.decode_event_with_address(address, &log).await;
2284
2285 assert_eq!(decoded.name.as_deref(), Some("log_string"));
2286 assert_eq!(decoded.params.unwrap()[0].0, "val");
2287 }
2288
2289 #[tokio::test]
2290 async fn address_scoped_abis_decode_all_registered_events() {
2291 let address = Address::repeat_byte(0x12);
2292 let proxy = JsonAbi::parse(["event ProxyEvent()"]).unwrap();
2293 let implementation = JsonAbi::parse(["event ImplementationEvent()"]).unwrap();
2294 let decoder = CallTraceDecoderBuilder::new()
2295 .with_address_events(address, &implementation)
2296 .with_address_events(address, &proxy)
2297 .build();
2298
2299 for event in proxy.events().chain(implementation.events()) {
2300 let log = LogData::new_unchecked(vec![event.selector()], Default::default());
2301 let decoded = decoder.decode_event_with_address(address, &log).await;
2302 assert_eq!(decoded.name.as_deref(), Some(event.name.as_str()));
2303 assert!(decoder.decode_event_with_address(Address::ZERO, &log).await.name.is_none());
2304 }
2305 }
2306
2307 #[tokio::test]
2308 async fn clearing_addresses_removes_regular_and_anonymous_events() {
2309 let address = Address::repeat_byte(0x12);
2310 let abi = JsonAbi::parse([
2311 "event ScopedValue(uint256 value)",
2312 "event AnonymousValue(uint256 value) anonymous",
2313 ])
2314 .unwrap();
2315 let mut decoder = CallTraceDecoderBuilder::new()
2316 .with_address_events(address, &abi)
2317 .with_address_events(address, &abi)
2318 .build();
2319 let global = Event::parse("event GlobalValue()").unwrap();
2320 let global_log = LogData::new_unchecked(vec![global.selector()], Default::default());
2321 decoder.push_event(global);
2322
2323 for event in abi.events() {
2324 let topics = if event.anonymous { vec![] } else { vec![event.selector()] };
2325 let log = LogData::new_unchecked(topics, (U256::from(7),).abi_encode().into());
2326 assert_eq!(
2327 decoder.decode_event_with_address(address, &log).await.name.as_deref(),
2328 Some(event.name.as_str())
2329 );
2330
2331 let mut cleared = decoder.clone();
2332 cleared.clear_addresses();
2333 assert!(cleared.decode_event_with_address(address, &log).await.name.is_none());
2334 assert_eq!(
2335 cleared.decode_event(&global_log).await.name.as_deref(),
2336 Some("GlobalValue")
2337 );
2338 }
2339 }
2340
2341 #[tokio::test]
2342 async fn canonical_address_events_require_a_unique_match() {
2343 let address = Address::repeat_byte(0x12);
2344 let implementation =
2345 JsonAbi::parse(["event Value(address indexed who, uint256 amount)"]).unwrap();
2346 let proxy = JsonAbi::parse(["event Value(address who, uint256 indexed amount)"]).unwrap();
2347 let event = proxy.events().next().unwrap();
2348 let log = LogData::new_unchecked(
2349 vec![event.selector(), U256::from(42).into()],
2350 (Address::repeat_byte(0x34),).abi_encode().into(),
2351 );
2352 let decoder = CallTraceDecoderBuilder::new()
2353 .with_address_events(address, &implementation)
2354 .with_address_events(address, &proxy)
2355 .build();
2356
2357 let decoded = decoder.decode_event_with_address_signature(address, &log).await;
2358 assert!(decoded.name.is_none());
2359 assert!(decoded.params.is_none());
2360 }
2361
2362 #[tokio::test]
2363 async fn canonical_global_events_require_a_unique_indexed_placement() {
2364 let known = Event::parse(
2365 "event AmbiguousLayout(address indexed from, address indexed to, uint256 amount)",
2366 )
2367 .unwrap();
2368 let emitted = Event::parse(
2369 "event AmbiguousLayout(address from, address indexed to, uint256 indexed amount)",
2370 )
2371 .unwrap();
2372 assert_eq!(known.selector(), emitted.selector());
2373
2374 let address = Address::repeat_byte(0x12);
2375 let from = Address::repeat_byte(0x34);
2376 let to = Address::repeat_byte(0x56);
2377 let amount = U256::from(42);
2378 let log = LogData::new_unchecked(
2379 vec![emitted.selector(), to.into_word(), amount.into()],
2380 (from,).abi_encode().into(),
2381 );
2382 let mut decoder = CallTraceDecoder::new().clone();
2383 decoder.push_event(known);
2384
2385 let decoded = decoder.decode_event_with_address_signature(address, &log).await;
2386 assert!(decoded.name.is_none());
2387 assert!(decoded.params.is_none());
2388
2389 decoder.push_address_event(address, emitted);
2390 let decoded = decoder.decode_event_with_address_signature(address, &log).await;
2391 assert_eq!(decoded.name.as_deref(), Some("AmbiguousLayout(address,address,uint256)"));
2392 let params = decoded.params.unwrap();
2393 assert_eq!(params[0].2, DynSolValue::Address(from));
2394 assert_eq!(params[1].2, DynSolValue::Address(to));
2395 assert_eq!(params[2].2, DynSolValue::Uint(amount, 256));
2396 }
2397
2398 #[tokio::test]
2399 async fn identified_events_preserve_legacy_indexed_placement_for_traces() {
2400 let event = Event::parse(
2401 "event DecoderAmbiguousIndexedPlacement(address indexed owner, uint256 id)",
2402 )
2403 .unwrap();
2404 let mut abi = JsonAbi::new();
2405 abi.events.insert(event.name.clone(), vec![event.clone()]);
2406 let log = LogData::new_unchecked(
2407 vec![event.selector(), U256::from(42).into()],
2408 (Address::repeat_byte(0x34),).abi_encode().into(),
2409 );
2410 let identifier = SignaturesIdentifier::new_offline_with_abis([&abi]).unwrap();
2411 let decoder = CallTraceDecoderBuilder::new().with_signature_identifier(identifier).build();
2412
2413 let decoded = decoder.decode_event(&log).await;
2414 assert_eq!(decoded.name.as_deref(), Some("DecoderAmbiguousIndexedPlacement"));
2415
2416 let decoded = decoder.decode_event_with_address_signature(Address::ZERO, &log).await;
2417 assert!(decoded.name.is_none());
2418 assert!(decoded.params.is_none());
2419 }
2420
2421 #[tokio::test]
2422 async fn address_scoped_anonymous_events_require_a_unique_match() {
2423 let address = Address::repeat_byte(0x12);
2424 let abi = JsonAbi::parse(["event AnonymousValue(uint256 value) anonymous"]).unwrap();
2425 let log = LogData::new_unchecked(Vec::new(), (U256::from(7),).abi_encode().into());
2426 let decoded = CallTraceDecoderBuilder::new()
2427 .with_address_events(address, &abi)
2428 .build()
2429 .decode_event_with_address(address, &log)
2430 .await;
2431 assert_eq!(decoded.name.as_deref(), Some("AnonymousValue"));
2432
2433 let abi = JsonAbi::parse([
2434 "event AnonymousValue(uint256 value) anonymous",
2435 "event AnonymousAmount(uint256 amount) anonymous",
2436 ])
2437 .unwrap();
2438 let decoder = CallTraceDecoderBuilder::new().with_address_events(address, &abi).build();
2439
2440 let decoded = decoder.decode_event_with_address(address, &log).await;
2441
2442 assert!(decoded.name.is_none());
2443 assert!(decoded.params.is_none());
2444
2445 let abi = JsonAbi::parse([
2446 "event RegularValue(uint256 value)",
2447 "event AnonymousValue(uint256 indexed value) anonymous",
2448 ])
2449 .unwrap();
2450 let regular = abi.events().find(|event| !event.anonymous).unwrap();
2451 let log = LogData::new_unchecked(vec![regular.selector()], Default::default());
2452 let decoded = CallTraceDecoderBuilder::new()
2453 .with_address_events(address, &abi)
2454 .build()
2455 .decode_event_with_address_signature(address, &log)
2456 .await;
2457 assert_eq!(decoded.name.as_deref(), Some("AnonymousValue(uint256)"));
2458
2459 let abi = JsonAbi::parse([
2460 "event RegularValue()",
2461 "event AnonymousValue(bytes32 indexed value) anonymous",
2462 ])
2463 .unwrap();
2464 let regular = abi.events().find(|event| !event.anonymous).unwrap();
2465 let log = LogData::new_unchecked(vec![regular.selector()], Default::default());
2466 let decoder = CallTraceDecoderBuilder::new().with_address_events(address, &abi).build();
2467 let decoded = decoder.decode_event_with_address_signature(address, &log).await;
2468 assert!(decoded.name.is_none());
2469 assert!(decoded.params.is_none());
2470 let decoded = decoder.decode_event_with_address(address, &log).await;
2471 assert!(decoded.name.is_none());
2472 assert!(decoded.params.is_none());
2473 }
2474
2475 #[test]
2476 fn compact_labels_hide_address_in_trace_parameters() {
2477 let address = Address::with_last_byte(1);
2478 let value = DynSolValue::Address(address);
2479 let tracing = TracingConfig {
2480 labels: AddressHashMap::from_iter([(address, "Alice".to_string())]),
2481 ..Default::default()
2482 };
2483 let decoder = CallTraceDecoderBuilder::new().with_tracing_config(&tracing).build();
2484 assert_eq!(decoder.format_value(&value), format!("Alice: [{address}]"));
2485
2486 let tracing = TracingConfig { compact_labels: true, ..tracing };
2487 let decoder = CallTraceDecoderBuilder::new().with_tracing_config(&tracing).build();
2488 assert_eq!(decoder.format_value(&value), "Alice");
2489
2490 let tracing = TracingConfig { disable_labels: true, ..tracing };
2491 let decoder = CallTraceDecoderBuilder::new().with_tracing_config(&tracing).build();
2492 assert_eq!(decoder.format_value(&value), address.to_string());
2493 }
2494
2495 #[test]
2496 fn configured_labels_survive_address_reset() {
2497 let configured = address!("0x0000000000000000000000000000000000000100");
2498 let discovered = address!("0x0000000000000000000000000000000000000200");
2499 let tracing = TracingConfig {
2500 labels: AddressHashMap::from_iter([(configured, "configured".to_string())]),
2501 ..Default::default()
2502 };
2503 let mut decoder = CallTraceDecoderBuilder::new().with_tracing_config(&tracing).build();
2504 decoder.labels.insert(discovered, "discovered".to_string());
2505
2506 decoder.clear_addresses();
2507
2508 assert_eq!(decoder.labels.get(&configured).map(String::as_str), Some("configured"));
2509 assert!(!decoder.labels.contains_key(&discovered));
2510 }
2511
2512 #[tokio::test]
2513 async fn test_decode_constructor_input() {
2514 let address = Address::repeat_byte(0x12);
2515 let constructor = Constructor::parse("constructor(uint256 amount, address owner)").unwrap();
2516 let args = constructor
2517 .abi_encode_input(&[
2518 DynSolValue::Uint(U256::from(42), 256),
2519 DynSolValue::Address(Address::repeat_byte(0x34)),
2520 ])
2521 .unwrap();
2522 let mut data = vec![0x60, 0x80, 0x60, 0x40];
2523 let offset = data.len();
2524 data.extend_from_slice(&args);
2525 let trace = CallTrace {
2526 address,
2527 kind: revm_inspectors::tracing::types::CallKind::Create,
2528 data: data.into(),
2529 ..Default::default()
2530 };
2531 let mut decoder = CallTraceDecoder::new().clone();
2532 decoder.constructors_by_address.insert(address, constructor);
2533 decoder.constructor_args_offsets.insert(address, offset);
2534
2535 let decoded = decoder.decode_function(&trace).await;
2536 let call_data = decoded.call_data.expect("constructor input should decode");
2537
2538 assert_eq!(call_data.signature, "constructor(uint256,address)");
2539 assert_eq!(call_data.args[0], "42");
2540 assert_eq!(call_data.args[1], format!("{}", Address::repeat_byte(0x34)));
2541 }
2542
2543 #[test]
2544 fn test_should_redact() {
2545 let mut decoder = CallTraceDecoder::new().clone();
2546 decoder.labels.insert(Address::repeat_byte(0x22), "signer".to_string());
2547
2548 let expected_revert_bytes4 = vec![0xde, 0xad, 0xbe, 0xef];
2549 let expect_revert_bytes4_data = Function::parse("expectRevert(bytes4)")
2550 .unwrap()
2551 .abi_encode_input(&[DynSolValue::FixedBytes(
2552 B256::right_padding_from(expected_revert_bytes4.as_slice()),
2553 4,
2554 )])
2555 .unwrap();
2556
2557 let expected_revert_bytes = hex!(
2558 "08c379a000000000000000000000000000000000000000000000000000000000\
2559 0000002000000000000000000000000000000000000000000000000000000000\
2560 00000004626f6f6d000000000000000000000000000000000000000000000000"
2561 )
2562 .to_vec();
2563 let expect_revert_bytes_data = Function::parse("expectRevert(bytes)")
2564 .unwrap()
2565 .abi_encode_input(&[DynSolValue::Bytes(expected_revert_bytes.clone())])
2566 .unwrap();
2567
2568 let reverter = Address::repeat_byte(0x11);
2569 let expect_revert_bytes4_address_data = Function::parse("expectRevert(bytes4,address)")
2570 .unwrap()
2571 .abi_encode_input(&[
2572 DynSolValue::FixedBytes(
2573 B256::right_padding_from(expected_revert_bytes4.as_slice()),
2574 4,
2575 ),
2576 DynSolValue::Address(reverter),
2577 ])
2578 .unwrap();
2579
2580 let count = 42_u64;
2581 let expect_revert_bytes_count_data = Function::parse("expectRevert(bytes,uint64)")
2582 .unwrap()
2583 .abi_encode_input(&[
2584 DynSolValue::Bytes(expected_revert_bytes.clone()),
2585 DynSolValue::Uint(alloy_primitives::U256::from(count), 64),
2586 ])
2587 .unwrap();
2588
2589 let expect_revert_bytes_address_count_data =
2590 Function::parse("expectRevert(bytes,address,uint64)")
2591 .unwrap()
2592 .abi_encode_input(&[
2593 DynSolValue::Bytes(expected_revert_bytes.clone()),
2594 DynSolValue::Address(reverter),
2595 DynSolValue::Uint(alloy_primitives::U256::from(count), 64),
2596 ])
2597 .unwrap();
2598
2599 let expect_revert_runtime_data = expected_revert_bytes4.clone();
2600
2601 let cheatcode_input_test_cases = vec![
2603 (
2605 "expectRevert(bytes4)",
2606 expect_revert_bytes4_data,
2607 Some(vec![decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None)]),
2608 ),
2609 (
2610 "expectRevert(bytes)",
2611 expect_revert_bytes_data,
2612 Some(vec![decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None)]),
2613 ),
2614 (
2615 "expectRevert(bytes4)",
2616 expect_revert_runtime_data.clone(),
2617 Some(vec![
2618 decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None),
2619 ]),
2620 ),
2621 (
2622 "expectRevert(bytes4,address)",
2623 expect_revert_bytes4_address_data,
2624 Some(vec![
2625 decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None),
2626 decoder.format_value(&DynSolValue::Address(reverter)),
2627 ]),
2628 ),
2629 (
2630 "expectRevert(bytes,uint64)",
2631 expect_revert_bytes_count_data,
2632 Some(vec![
2633 decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None),
2634 decoder
2635 .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)),
2636 ]),
2637 ),
2638 (
2639 "expectRevert(bytes,address,uint64)",
2640 expect_revert_bytes_address_count_data,
2641 Some(vec![
2642 decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None),
2643 decoder.format_value(&DynSolValue::Address(reverter)),
2644 decoder
2645 .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)),
2646 ]),
2647 ),
2648 (
2649 "expectRevert()",
2650 expect_revert_runtime_data.clone(),
2651 Some(vec![
2652 decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None),
2653 ]),
2654 ),
2655 ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2657 ("createEd25519Key(bytes32)", vec![], Some(vec!["<pk>".to_string()])),
2658 ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
2659 ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2660 ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
2661 ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
2662 ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
2663 ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
2664 ("publicKeyEd25519(bytes32)", vec![], Some(vec!["<pk>".to_string()])),
2665 ("publicKeyP256(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2666 ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2667 ("rememberKeys(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
2668 ("rememberKeys(string,string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
2669 ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2672 ("broadcast()", vec![], None), ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2674 ("startBroadcast()", vec![], None), ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
2676 ("getNonce(address)", vec![], None), (
2680 "sign(uint256,bytes32)",
2681 hex!(
2682 "
2683 e341eaa4
2684 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2685 0000000000000000000000000000000000000000000000000000000000000000
2686 "
2687 )
2688 .to_vec(),
2689 Some(vec![
2690 "\"<pk>\"".to_string(),
2691 "0x0000000000000000000000000000000000000000000000000000000000000000"
2692 .to_string(),
2693 ]),
2694 ),
2695 (
2696 "signP256(uint256,bytes32)",
2697 hex!(
2698 "
2699 83211b40
2700 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2701 0000000000000000000000000000000000000000000000000000000000000000
2702 "
2703 )
2704 .to_vec(),
2705 Some(vec![
2706 "\"<pk>\"".to_string(),
2707 "0x0000000000000000000000000000000000000000000000000000000000000000"
2708 .to_string(),
2709 ]),
2710 ),
2711 (
2712 "signCompact(uint256,bytes32)",
2713 hex!(
2714 "
2715 cc2a781f
2716 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2717 0000000000000000000000000000000000000000000000000000000000000000
2718 "
2719 )
2720 .to_vec(),
2721 Some(vec![
2722 "\"<pk>\"".to_string(),
2723 "0x0000000000000000000000000000000000000000000000000000000000000000"
2724 .to_string(),
2725 ]),
2726 ),
2727 (
2728 "signCompact((address,uint256,uint256,uint256),bytes32)",
2730 hex!(
2731 "
2732 3d0e292f
2733 0000000000000000000000001111111111111111111111111111111111111111
2734 0000000000000000000000000000000000000000000000000000000000000001
2735 0000000000000000000000000000000000000000000000000000000000000002
2736 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2737 0000000000000000000000000000000000000000000000000000000000000000
2738 "
2739 )
2740 .to_vec(),
2741 Some(vec![
2742 "\"<pk>\"".to_string(),
2743 "0x0000000000000000000000000000000000000000000000000000000000000000"
2744 .to_string(),
2745 ]),
2746 ),
2747 (
2748 "signCompact(bytes32)",
2750 hex!(
2751 "
2752 a282dc4b
2753 0000000000000000000000000000000000000000000000000000000000000000
2754 "
2755 )
2756 .to_vec(),
2757 Some(vec![
2758 "0x0000000000000000000000000000000000000000000000000000000000000000"
2759 .to_string(),
2760 ]),
2761 ),
2762 (
2763 "signCompact(address,bytes32)",
2765 hex!(
2766 "
2767 8e2f97bf
2768 0000000000000000000000001111111111111111111111111111111111111111
2769 0000000000000000000000000000000000000000000000000000000000000000
2770 "
2771 )
2772 .to_vec(),
2773 Some(vec![
2774 "0x1111111111111111111111111111111111111111".to_string(),
2775 "0x0000000000000000000000000000000000000000000000000000000000000000"
2776 .to_string(),
2777 ]),
2778 ),
2779 (
2780 "signWithNonceUnsafe(uint256,bytes32,uint256)",
2781 hex!(
2782 "
2783 2012783a
2784 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2785 0000000000000000000000000000000000000000000000000000000000000000
2786 0000000000000000000000000000000000000000000000000000000000000001
2787 "
2788 )
2789 .to_vec(),
2790 Some(vec![
2791 "\"<pk>\"".to_string(),
2792 "0x0000000000000000000000000000000000000000000000000000000000000000"
2793 .to_string(),
2794 "\"<nonce>\"".to_string(),
2795 ]),
2796 ),
2797 (
2798 "signKeychain(uint256,address,bytes32)",
2799 hex!(
2800 "
2801 5804c690
2802 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2803 0000000000000000000000001111111111111111111111111111111111111111
2804 0000000000000000000000000000000000000000000000000000000000000000
2805 "
2806 )
2807 .to_vec(),
2808 Some(vec![
2809 "\"<pk>\"".to_string(),
2810 "0x1111111111111111111111111111111111111111".to_string(),
2811 "0x0000000000000000000000000000000000000000000000000000000000000000"
2812 .to_string(),
2813 ]),
2814 ),
2815 (
2816 "signKeychainAdmin(uint256,address,bytes32)",
2817 hex!(
2818 "
2819 bc5fb2f7
2820 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2821 0000000000000000000000001111111111111111111111111111111111111111
2822 0000000000000000000000000000000000000000000000000000000000000000
2823 "
2824 )
2825 .to_vec(),
2826 Some(vec![
2827 "\"<pk>\"".to_string(),
2828 "0x1111111111111111111111111111111111111111".to_string(),
2829 "0x0000000000000000000000000000000000000000000000000000000000000000"
2830 .to_string(),
2831 ]),
2832 ),
2833 (
2834 "signKeychain(uint256,address,bytes32)",
2836 hex!(
2837 "
2838 5804c690
2839 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2840 0000000000000000000000002222222222222222222222222222222222222222
2841 0000000000000000000000000000000000000000000000000000000000000000
2842 "
2843 )
2844 .to_vec(),
2845 Some(vec![
2846 "\"<pk>\"".to_string(),
2847 "signer: [0x2222222222222222222222222222222222222222]".to_string(),
2848 "0x0000000000000000000000000000000000000000000000000000000000000000"
2849 .to_string(),
2850 ]),
2851 ),
2852 (
2853 "signEd25519(bytes,bytes,bytes32)",
2856 hex!(
2857 "
2858 ef609c65
2859 0000000000000000000000000000000000000000000000000000000000000060
2860 00000000000000000000000000000000000000000000000000000000000000a0
2861 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2862 0000000000000000000000000000000000000000000000000000000000000002
2863 6e73000000000000000000000000000000000000000000000000000000000000
2864 0000000000000000000000000000000000000000000000000000000000000003
2865 6d73670000000000000000000000000000000000000000000000000000000000
2866 "
2867 )
2868 .to_vec(),
2869 Some(vec!["0x6e73".to_string(), "0x6d7367".to_string(), "\"<pk>\"".to_string()]),
2870 ),
2871 (
2872 "createFork(string)",
2874 hex!(
2875 "
2876 31ba3498
2877 0000000000000000000000000000000000000000000000000000000000000020
2878 000000000000000000000000000000000000000000000000000000000000002c
2879 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
2880 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
2881 "
2882 )
2883 .to_vec(),
2884 Some(vec!["\"<rpc url>\"".to_string()]),
2885 ),
2886 (
2887 "createFork(string)",
2889 hex!(
2890 "
2891 31ba3498
2892 0000000000000000000000000000000000000000000000000000000000000020
2893 000000000000000000000000000000000000000000000000000000000000002a
2894 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
2895 76322f6170695f6b657900000000000000000000000000000000000000000000
2896 "
2897 )
2898 .to_vec(),
2899 Some(vec!["\"<rpc url>\"".to_string()]),
2900 ),
2901 (
2902 "createFork(string)",
2904 hex!(
2905 "
2906 31ba3498
2907 0000000000000000000000000000000000000000000000000000000000000020
2908 0000000000000000000000000000000000000000000000000000000000000007
2909 6d61696e6e657400000000000000000000000000000000000000000000000000
2910 "
2911 )
2912 .to_vec(),
2913 Some(vec!["\"mainnet\"".to_string()]),
2914 ),
2915 (
2916 "createFork(string,uint256)",
2918 hex!(
2919 "
2920 6ba3ba2b
2921 0000000000000000000000000000000000000000000000000000000000000040
2922 0000000000000000000000000000000000000000000000000000000000000001
2923 000000000000000000000000000000000000000000000000000000000000002c
2924 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
2925 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
2926 "
2927 )
2928 .to_vec(),
2929 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
2930 ),
2931 (
2932 "createFork(string,uint256)",
2934 hex!(
2935 "
2936 6ba3ba2b
2937 0000000000000000000000000000000000000000000000000000000000000040
2938 0000000000000000000000000000000000000000000000000000000000000001
2939 0000000000000000000000000000000000000000000000000000000000000007
2940 6d61696e6e657400000000000000000000000000000000000000000000000000
2941 "
2942 )
2943 .to_vec(),
2944 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
2945 ),
2946 (
2947 "createFork(string,bytes32)",
2949 hex!(
2950 "
2951 7ca29682
2952 0000000000000000000000000000000000000000000000000000000000000040
2953 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2954 000000000000000000000000000000000000000000000000000000000000002c
2955 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
2956 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
2957 "
2958 )
2959 .to_vec(),
2960 Some(vec![
2961 "\"<rpc url>\"".to_string(),
2962 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
2963 .to_string(),
2964 ]),
2965 ),
2966 (
2967 "createFork(string,bytes32)",
2970 hex!(
2971 "
2972 7ca29682
2973 0000000000000000000000000000000000000000000000000000000000000040
2974 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2975 0000000000000000000000000000000000000000000000000000000000000007
2976 6d61696e6e657400000000000000000000000000000000000000000000000000
2977 "
2978 )
2979 .to_vec(),
2980 Some(vec![
2981 "\"mainnet\"".to_string(),
2982 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
2983 .to_string(),
2984 ]),
2985 ),
2986 (
2987 "createSelectFork(string)",
2989 hex!(
2990 "
2991 98680034
2992 0000000000000000000000000000000000000000000000000000000000000020
2993 000000000000000000000000000000000000000000000000000000000000002c
2994 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
2995 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
2996 "
2997 )
2998 .to_vec(),
2999 Some(vec!["\"<rpc url>\"".to_string()]),
3000 ),
3001 (
3002 "createSelectFork(string)",
3004 hex!(
3005 "
3006 98680034
3007 0000000000000000000000000000000000000000000000000000000000000020
3008 0000000000000000000000000000000000000000000000000000000000000007
3009 6d61696e6e657400000000000000000000000000000000000000000000000000
3010 "
3011 )
3012 .to_vec(),
3013 Some(vec!["\"mainnet\"".to_string()]),
3014 ),
3015 (
3016 "createSelectFork(string,uint256)",
3018 hex!(
3019 "
3020 71ee464d
3021 0000000000000000000000000000000000000000000000000000000000000040
3022 0000000000000000000000000000000000000000000000000000000000000001
3023 000000000000000000000000000000000000000000000000000000000000002c
3024 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
3025 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
3026 "
3027 )
3028 .to_vec(),
3029 Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
3030 ),
3031 (
3032 "createSelectFork(string,uint256)",
3034 hex!(
3035 "
3036 71ee464d
3037 0000000000000000000000000000000000000000000000000000000000000040
3038 0000000000000000000000000000000000000000000000000000000000000001
3039 0000000000000000000000000000000000000000000000000000000000000007
3040 6d61696e6e657400000000000000000000000000000000000000000000000000
3041 "
3042 )
3043 .to_vec(),
3044 Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
3045 ),
3046 (
3047 "createSelectFork(string,bytes32)",
3049 hex!(
3050 "
3051 84d52b7a
3052 0000000000000000000000000000000000000000000000000000000000000040
3053 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
3054 000000000000000000000000000000000000000000000000000000000000002c
3055 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
3056 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
3057 "
3058 )
3059 .to_vec(),
3060 Some(vec![
3061 "\"<rpc url>\"".to_string(),
3062 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
3063 .to_string(),
3064 ]),
3065 ),
3066 (
3067 "createSelectFork(string,bytes32)",
3070 hex!(
3071 "
3072 84d52b7a
3073 0000000000000000000000000000000000000000000000000000000000000040
3074 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
3075 0000000000000000000000000000000000000000000000000000000000000007
3076 6d61696e6e657400000000000000000000000000000000000000000000000000
3077 "
3078 )
3079 .to_vec(),
3080 Some(vec![
3081 "\"mainnet\"".to_string(),
3082 "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
3083 .to_string(),
3084 ]),
3085 ),
3086 (
3087 "rpc(string,string,string)",
3089 hex!(
3090 "
3091 0199a220
3092 0000000000000000000000000000000000000000000000000000000000000060
3093 00000000000000000000000000000000000000000000000000000000000000c0
3094 0000000000000000000000000000000000000000000000000000000000000100
3095 000000000000000000000000000000000000000000000000000000000000002c
3096 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
3097 6d2f76322f6170695f6b65790000000000000000000000000000000000000000
3098 000000000000000000000000000000000000000000000000000000000000000e
3099 6574685f67657442616c616e6365000000000000000000000000000000000000
3100 0000000000000000000000000000000000000000000000000000000000000034
3101 5b22307835353165373738343737386566386530343865343935646634396632
3102 363134663834613466316463222c22307830225d000000000000000000000000
3103 "
3104 )
3105 .to_vec(),
3106 Some(vec![
3107 "\"<rpc url>\"".to_string(),
3108 "\"eth_getBalance\"".to_string(),
3109 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
3110 .to_string(),
3111 ]),
3112 ),
3113 (
3114 "rpc(string,string,string)",
3117 hex!(
3118 "
3119 0199a220
3120 0000000000000000000000000000000000000000000000000000000000000060
3121 00000000000000000000000000000000000000000000000000000000000000a0
3122 00000000000000000000000000000000000000000000000000000000000000e0
3123 0000000000000000000000000000000000000000000000000000000000000007
3124 6d61696e6e657400000000000000000000000000000000000000000000000000
3125 000000000000000000000000000000000000000000000000000000000000000e
3126 6574685f67657442616c616e6365000000000000000000000000000000000000
3127 0000000000000000000000000000000000000000000000000000000000000034
3128 5b22307835353165373738343737386566386530343865343935646634396632
3129 363134663834613466316463222c22307830225d000000000000000000000000
3130 "
3131 )
3132 .to_vec(),
3133 Some(vec![
3134 "\"mainnet\"".to_string(),
3135 "\"eth_getBalance\"".to_string(),
3136 "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
3137 .to_string(),
3138 ]),
3139 ),
3140 ];
3141
3142 let cheatcode_output_test_cases = vec![
3144 ("createEd25519Key(bytes32)", Some("<pk>".to_string())),
3146 ("createWallet(string)", Some("<pk>".to_string())),
3147 ("deriveKey(string,uint32)", Some("<pk>".to_string())),
3148 ("rpcUrl(string)", Some("<rpc url>".to_string())),
3150 ("rpcUrls()", Some("<rpc url>".to_string())),
3151 ("rpcUrlStructs()", Some("<rpc url>".to_string())),
3152 ];
3153
3154 for (function_signature, data, expected) in cheatcode_input_test_cases {
3155 let function = Function::parse(function_signature).unwrap();
3156 let result = decoder.decode_cheatcode_inputs(&function, &data);
3157 assert_eq!(result, expected, "Input case failed for: {function_signature}");
3158 }
3159
3160 for (function_signature, expected) in cheatcode_output_test_cases {
3161 let function = Function::parse(function_signature).unwrap();
3162 let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
3163 assert_eq!(result, expected, "Output case failed for: {function_signature}");
3164 }
3165 }
3166
3167 #[tokio::test]
3168 async fn test_should_redact_end_to_end() {
3169 let decoder = CallTraceDecoder::new();
3170 let pk_hex = "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6";
3171 let pk = hex!("7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6");
3172 let cheatcode_trace = |data: Vec<u8>| CallTrace {
3173 address: CHEATCODE_ADDRESS,
3174 data: data.into(),
3175 success: true,
3176 ..Default::default()
3177 };
3178
3179 let call = Vm::sign_1Call { privateKey: U256::from_be_bytes(pk), digest: B256::ZERO };
3182 let decoded = decoder.decode_function(&cheatcode_trace(call.abi_encode())).await;
3183 let call_data = decoded.call_data.expect("sign should decode");
3184 assert_eq!(call_data.signature, "sign(uint256,bytes32)");
3185 assert_eq!(call_data.args[0], "\"<pk>\"");
3186 assert!(!call_data.args.join(",").contains(pk_hex));
3187
3188 let call = Vm::signEd25519Call {
3190 namespace: b"ns".into(),
3191 message: b"msg".into(),
3192 privateKey: B256::from(pk),
3193 };
3194 let decoded = decoder.decode_function(&cheatcode_trace(call.abi_encode())).await;
3195 let call_data = decoded.call_data.expect("signEd25519 should decode");
3196 assert_eq!(call_data.signature, "signEd25519(bytes,bytes,bytes32)");
3197 assert_eq!(call_data.args[2], "\"<pk>\"");
3198 assert!(!call_data.args.join(",").contains(pk_hex));
3199
3200 let call = Vm::sign_1Call { privateKey: U256::from_be_bytes(pk), digest: B256::ZERO };
3203 let mut data = call.abi_encode();
3204 data.push(0xff);
3205 let decoded = decoder.decode_function(&cheatcode_trace(data)).await;
3206 let call_data = decoded.call_data.expect("malformed calldata must not fall back to raw");
3207 assert_eq!(call_data.signature, "sign(uint256,bytes32)");
3208 assert!(!call_data.args.join(",").contains(pk_hex));
3209
3210 let call = Vm::deriveKey_0Call {
3213 mnemonic: "test test test test test test test test test test test junk".to_string(),
3214 index: 0,
3215 };
3216 let mut data = call.abi_encode();
3217 *data.last_mut().unwrap() = 0xff;
3218 data.push(0xff);
3219 let decoded = decoder.decode_function(&cheatcode_trace(data)).await;
3220 let call_data = decoded.call_data.expect("suffixed calldata must not fall back to raw");
3221 assert_eq!(call_data.args, vec!["<pk>".to_string()]);
3222
3223 let data = Vm::sign_1Call::SELECTOR.iter().copied().chain(pk).collect::<Vec<u8>>();
3225 let decoded = decoder.decode_function(&cheatcode_trace(data)).await;
3226 let call_data = decoded.call_data.expect("truncated calldata must not fall back to raw");
3227 assert_eq!(call_data.signature, "sign(uint256,bytes32)");
3228 assert_eq!(call_data.args, vec!["<redacted>".to_string()]);
3229 }
3230
3231 #[tokio::test]
3232 async fn test_tempo_decode_preserves_existing_labels() {
3233 let decoder = CallTraceDecoder::new();
3234 let trace = CallTrace { address: PATH_USD_ADDRESS, success: true, ..Default::default() };
3235
3236 let decoded = decoder.decode_function(&trace).await;
3237 assert_eq!(decoded.label.as_deref(), Some("PathUSD"));
3238 }
3239
3240 #[tokio::test]
3241 async fn test_tempo_decode_labels_ousd() {
3242 let decoder = CallTraceDecoder::new();
3243 let transfer = ITIP20::transferCall {
3244 to: address!("0x0000000000000000000000000000000000000def"),
3245 amount: U256::from(1_000_000u64),
3246 };
3247 let trace = CallTrace {
3248 address: OUSD_ADDRESS,
3249 data: transfer.abi_encode().into(),
3250 success: true,
3251 ..Default::default()
3252 };
3253
3254 let decoded = decoder.decode_function(&trace).await;
3255 assert_eq!(decoded.label.as_deref(), Some("OUSD"));
3256 let call_data = decoded.call_data.expect("OUSD transfer should decode");
3257 assert_eq!(call_data.signature, "transfer(address,uint256)");
3258 }
3259
3260 #[tokio::test]
3261 async fn test_t5_decode_does_not_synthesize_general_target_label() {
3262 let mut decoder = CallTraceDecoder::new().clone();
3263 decoder.chain_id = Some(4217);
3264 let trace = CallTrace {
3265 address: address!("0x0000000000000000000000000000000000000123"),
3266 depth: 0,
3267 success: true,
3268 ..Default::default()
3269 };
3270
3271 let decoded = decoder.decode_function(&trace).await;
3272 assert_eq!(decoded.label, None);
3273 }
3274
3275 #[tokio::test]
3276 async fn test_t5_tip20_logo_uri_calls_and_events_decode() {
3277 let decoder = CallTraceDecoder::new();
3278
3279 let call = ITIP20::setLogoURICall { newLogoURI: "https://example.com/logo.png".into() };
3280 let trace = CallTrace {
3281 address: PATH_USD_ADDRESS,
3282 data: call.abi_encode().into(),
3283 success: true,
3284 ..Default::default()
3285 };
3286 let decoded = decoder.decode_function(&trace).await;
3287 let call_data = decoded.call_data.expect("setLogoURI should decode");
3288 assert_eq!(call_data.signature, "setLogoURI(string)");
3289 assert_eq!(call_data.args, vec!["\"https://example.com/logo.png\"".to_string()]);
3290
3291 let call = ITIP20::logoURICall {};
3292 let trace = CallTrace {
3293 address: PATH_USD_ADDRESS,
3294 data: call.abi_encode().into(),
3295 success: true,
3296 ..Default::default()
3297 };
3298 let decoded = decoder.decode_function(&trace).await;
3299 assert_eq!(decoded.call_data.expect("logoURI should decode").signature, "logoURI()");
3300
3301 let event = ITIP20::LogoURIUpdated {
3302 updater: address!("0x0000000000000000000000000000000000000abc"),
3303 newLogoURI: "ipfs://logo".into(),
3304 };
3305 let decoded = decoder.decode_event(&event.encode_log_data()).await;
3306 assert_eq!(decoded.name.as_deref(), Some("LogoURIUpdated"));
3307 let params = decoded.params.expect("LogoURIUpdated params should decode");
3308 assert_eq!(params[0].0, "updater");
3309 assert!(
3310 params[0].1.to_ascii_lowercase().contains("0000000000000000000000000000000000000abc")
3311 );
3312 assert_eq!(params[1], ("newLogoURI".into(), "\"ipfs://logo\"".into()));
3313 }
3314
3315 #[tokio::test]
3316 async fn test_t5_tip20_factory_create_token_with_logo_decodes() {
3317 let decoder = CallTraceDecoder::new();
3318 let call = ITIP20Factory::createToken_1Call {
3319 name: "Example USD".into(),
3320 symbol: "xUSD".into(),
3321 currency: "USD".into(),
3322 quoteToken: PATH_USD_ADDRESS,
3323 admin: address!("0x0000000000000000000000000000000000000abc"),
3324 salt: B256::repeat_byte(0x11),
3325 logoURI: "https://example.com/xusd.png".into(),
3326 };
3327 let trace = CallTrace {
3328 address: TIP20_FACTORY_ADDRESS,
3329 data: call.abi_encode().into(),
3330 success: true,
3331 ..Default::default()
3332 };
3333 let decoded = decoder.decode_function(&trace).await;
3334 let call_data = decoded.call_data.expect("createToken overload should decode");
3335 assert_eq!(
3336 call_data.signature,
3337 "createToken(string,string,string,address,address,bytes32,string)"
3338 );
3339 assert_eq!(call_data.args[6], "\"https://example.com/xusd.png\"");
3340 }
3341
3342 #[tokio::test]
3343 async fn test_t5_stablecoin_dex_order_flipped_event_decodes() {
3344 let decoder = CallTraceDecoder::new();
3345 let event = IStablecoinDEX::OrderFlipped {
3346 orderId: 42,
3347 maker: address!("0x0000000000000000000000000000000000000abc"),
3348 token: PATH_USD_ADDRESS,
3349 amount: 1_000_000,
3350 isBid: false,
3351 tick: 100,
3352 flipTick: 100,
3353 };
3354 let decoded = decoder.decode_event(&event.encode_log_data()).await;
3355 assert_eq!(decoded.name.as_deref(), Some("OrderFlipped"));
3356 let params = decoded.params.expect("OrderFlipped params should decode");
3357 assert_eq!(params[0], ("orderId".into(), "42".into()));
3358 assert_eq!(params[4], ("isBid".into(), "false".into()));
3359 assert_eq!(params[5], ("tick".into(), "100".into()));
3360 assert_eq!(params[6], ("flipTick".into(), "100".into()));
3361 }
3362
3363 #[tokio::test]
3364 async fn test_t5_channel_reserve_call_and_event_decode() {
3365 let mut decoder = CallTraceDecoder::new().clone();
3366 decoder.chain_id = Some(4217);
3367
3368 let open = ITIP20ChannelReserve::openCall {
3369 payee: address!("0x0000000000000000000000000000000000000abc"),
3370 operator: Address::ZERO,
3371 token: PATH_USD_ADDRESS,
3372 deposit: U96::from(1_000_000u64),
3373 salt: B256::repeat_byte(0x22),
3374 authorizedSigner: Address::ZERO,
3375 };
3376 let trace = CallTrace {
3377 address: TIP20_CHANNEL_RESERVE_ADDRESS,
3378 data: open.abi_encode().into(),
3379 depth: 0,
3380 success: true,
3381 ..Default::default()
3382 };
3383 let decoded = decoder.decode_function(&trace).await;
3384 assert_eq!(decoded.label.as_deref(), Some("TIP20ChannelReserve"));
3385 assert_eq!(
3386 decoded.call_data.expect("open should decode").signature,
3387 "open(address,address,address,uint96,bytes32,address)"
3388 );
3389
3390 let transfer = ITIP20::transferCall {
3391 to: address!("0x0000000000000000000000000000000000000def"),
3392 amount: U256::from(1_000_000u64),
3393 };
3394 let trace = CallTrace {
3395 address: PATH_USD_ADDRESS,
3396 data: transfer.abi_encode().into(),
3397 depth: 0,
3398 success: true,
3399 ..Default::default()
3400 };
3401 let decoded = decoder.decode_function(&trace).await;
3402 assert_eq!(decoded.label.as_deref(), Some("PathUSD"));
3403 let json = serde_json::to_string(&decoded).expect("decoded trace serializes");
3404 assert!(json.contains(r#""label":"PathUSD""#));
3405 assert!(!json.contains("payment-lane"));
3406
3407 let balance_of = ITIP20::balanceOfCall {
3408 account: address!("0x0000000000000000000000000000000000000def"),
3409 };
3410 let trace = CallTrace {
3411 address: PATH_USD_ADDRESS,
3412 data: balance_of.abi_encode().into(),
3413 depth: 0,
3414 success: true,
3415 ..Default::default()
3416 };
3417 let decoded = decoder.decode_function(&trace).await;
3418 assert_eq!(decoded.label.as_deref(), Some("PathUSD"));
3419
3420 let event = ITIP20ChannelReserve::ChannelOpened {
3421 channelId: B256::repeat_byte(0x33),
3422 payer: address!("0x0000000000000000000000000000000000000123"),
3423 payee: address!("0x0000000000000000000000000000000000000abc"),
3424 operator: Address::ZERO,
3425 token: PATH_USD_ADDRESS,
3426 authorizedSigner: Address::ZERO,
3427 salt: B256::repeat_byte(0x22),
3428 expiringNonceHash: B256::repeat_byte(0x44),
3429 deposit: U96::from(1_000_000u64),
3430 };
3431 let decoded = decoder.decode_event(&event.encode_log_data()).await;
3432 assert_eq!(decoded.name.as_deref(), Some("ChannelOpened"));
3433 let params = decoded.params.expect("ChannelOpened params should decode");
3434 assert_eq!(params[0].0, "channelId");
3435 assert_eq!(params[8].0, "deposit");
3436 assert!(params[8].1.starts_with("1000000"));
3437 }
3438
3439 #[tokio::test]
3440 #[cfg(feature = "monad")]
3441 async fn test_decodes_monad_staking_precompile_call() {
3442 let trace = CallTrace {
3443 address: monad_revm::staking::STAKING_ADDRESS,
3444 data: getEpochCall::SELECTOR.into(),
3445 output:
3446 monad_revm::staking::interface::IMonadStaking::getEpochCall::abi_encode_returns(
3447 &monad_revm::staking::interface::IMonadStaking::getEpochReturn {
3448 epoch: 42,
3449 inEpochDelayPeriod: true,
3450 },
3451 )
3452 .into(),
3453 success: true,
3454 ..Default::default()
3455 };
3456
3457 let decoded = monad_decoder(MonadHardfork::MonadEight).decode_function(&trace).await;
3458
3459 assert_eq!(decoded.label.as_deref(), Some("Staking"));
3460 let call_data = decoded.call_data.expect("call data");
3461 assert_eq!(call_data.signature, "getEpoch()");
3462 assert!(call_data.args.is_empty());
3463 assert_eq!(decoded.return_data.as_deref(), Some("42, true"));
3464 }
3465
3466 #[tokio::test]
3467 #[cfg(feature = "monad")]
3468 async fn test_decodes_monad_staking_syscall() {
3469 let block_author = Address::repeat_byte(0x42);
3470 let trace = CallTrace {
3471 address: monad_revm::staking::STAKING_ADDRESS,
3472 data: monad::IMonadStakingSyscalls::syscallRewardCall { blockAuthor: block_author }
3473 .abi_encode()
3474 .into(),
3475 success: true,
3476 ..Default::default()
3477 };
3478
3479 let decoder = monad_decoder(MonadHardfork::MonadEight);
3480 let expected_author = decoder.format_value(&DynSolValue::Address(block_author));
3481 let decoded = decoder.decode_function(&trace).await;
3482
3483 assert_eq!(decoded.label.as_deref(), Some("Staking"));
3484 let call_data = decoded.call_data.expect("call data");
3485 assert_eq!(call_data.signature, "syscallReward(address)");
3486 assert_eq!(call_data.args, vec![expected_author]);
3487 }
3488
3489 #[tokio::test]
3490 #[cfg(feature = "monad")]
3491 async fn test_decodes_monad_reserve_balance_precompile_call() {
3492 let trace = CallTrace {
3493 address: monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3494 data: dippedIntoReserveCall::SELECTOR.into(),
3495 output: true.abi_encode().into(),
3496 success: true,
3497 ..Default::default()
3498 };
3499
3500 let decoded = monad_decoder(MonadHardfork::MonadNine).decode_function(&trace).await;
3501
3502 assert_eq!(decoded.label.as_deref(), Some("ReserveBalance"));
3503 let call_data = decoded.call_data.expect("call data");
3504 assert_eq!(call_data.signature, "dippedIntoReserve()");
3505 assert!(call_data.args.is_empty());
3506 assert_eq!(decoded.return_data.as_deref(), Some("true"));
3507 }
3508
3509 #[tokio::test]
3510 #[cfg(feature = "monad")]
3511 async fn test_decodes_monad_staking_precompile_event() {
3512 let event = Event::parse(
3513 "event Delegate(uint64 indexed validatorId,address indexed delegator,uint256 amount,uint64 activationEpoch)",
3514 )
3515 .unwrap();
3516 let delegator = Address::repeat_byte(0x11);
3517 let log = LogData::new_unchecked(
3518 vec![event.selector(), topic_from_u64(7), topic_from_address(delegator)],
3519 (U256::from(1000), 9_u64).abi_encode().into(),
3520 );
3521
3522 let decoder = monad_decoder(MonadHardfork::MonadEight);
3523 let decoded =
3524 decoder.decode_event_with_address(monad_revm::staking::STAKING_ADDRESS, &log).await;
3525
3526 assert_eq!(decoded.name.as_deref(), Some("Delegate"));
3527 let params = decoded.params.expect("params");
3528 assert_eq!(params[0], ("validatorId".to_string(), "7".to_string()));
3529 assert_eq!(params[1].0, "delegator");
3530 assert_eq!(params[2], ("amount".to_string(), "1000".to_string()));
3531 assert_eq!(params[3], ("activationEpoch".to_string(), "9".to_string()));
3532
3533 let collision = decoder
3534 .decode_event_with_address(
3535 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3536 &log,
3537 )
3538 .await;
3539 assert_eq!(collision.name, None);
3540 }
3541
3542 #[tokio::test]
3543 #[cfg(feature = "monad")]
3544 async fn test_monad_metadata_is_not_registered_for_ethereum() {
3545 let decoder = CallTraceDecoderBuilder::new().with_chain_id(Some(1)).build();
3546 for address in [
3547 foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS,
3548 monad_revm::staking::STAKING_ADDRESS,
3549 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3550 ] {
3551 assert!(!decoder.labels.contains_key(&address));
3552 }
3553
3554 let staking_trace = CallTrace {
3555 address: monad_revm::staking::STAKING_ADDRESS,
3556 data: getEpochCall::SELECTOR.into(),
3557 output:
3558 monad_revm::staking::interface::IMonadStaking::getEpochCall::abi_encode_returns(
3559 &monad_revm::staking::interface::IMonadStaking::getEpochReturn {
3560 epoch: 42,
3561 inEpochDelayPeriod: true,
3562 },
3563 )
3564 .into(),
3565 success: true,
3566 ..Default::default()
3567 };
3568 let staking = decoder.decode_function(&staking_trace).await;
3569 assert_ne!(
3570 staking.call_data.as_ref().map(|call| call.signature.as_str()),
3571 Some("getEpoch()")
3572 );
3573
3574 let reserve_trace = CallTrace {
3575 address: monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3576 data: dippedIntoReserveCall::SELECTOR.into(),
3577 output: true.abi_encode().into(),
3578 success: true,
3579 ..Default::default()
3580 };
3581 let reserve = decoder.decode_function(&reserve_trace).await;
3582 assert_ne!(
3583 reserve.call_data.as_ref().map(|call| call.signature.as_str()),
3584 Some("dippedIntoReserve()")
3585 );
3586
3587 let event = Event::parse(
3588 "event Delegate(uint64 indexed validatorId,address indexed delegator,uint256 amount,uint64 activationEpoch)",
3589 )
3590 .unwrap();
3591 let log = LogData::new_unchecked(
3592 vec![
3593 event.selector(),
3594 topic_from_u64(7),
3595 topic_from_address(Address::repeat_byte(0x11)),
3596 ],
3597 (U256::from(1000), 9_u64).abi_encode().into(),
3598 );
3599 for address in [
3600 monad_revm::staking::STAKING_ADDRESS,
3601 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3602 ] {
3603 let decoded = decoder.decode_event_with_address(address, &log).await;
3604 assert_eq!(decoded.name, None);
3605 }
3606 }
3607
3608 #[tokio::test]
3609 #[cfg(feature = "monad")]
3610 async fn test_monad_reserve_metadata_starts_at_monad_nine() {
3611 let mut monad_eight = monad_decoder(MonadHardfork::MonadEight);
3612 monad_eight.clear_addresses();
3613 assert_eq!(
3614 monad_eight
3615 .labels
3616 .get(&foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS)
3617 .map(String::as_str),
3618 Some("MonadVM")
3619 );
3620 assert_eq!(
3621 monad_eight.labels.get(&monad_revm::staking::STAKING_ADDRESS).map(String::as_str),
3622 Some("Staking")
3623 );
3624 assert!(
3625 !monad_eight
3626 .labels
3627 .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
3628 );
3629
3630 let trace = CallTrace {
3631 address: monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3632 data: dippedIntoReserveCall::SELECTOR.into(),
3633 output: true.abi_encode().into(),
3634 success: true,
3635 ..Default::default()
3636 };
3637 let before = monad_eight.decode_function(&trace).await;
3638 assert_ne!(
3639 before.call_data.as_ref().map(|call| call.signature.as_str()),
3640 Some("dippedIntoReserve()")
3641 );
3642
3643 let mut monad_nine = monad_decoder(MonadHardfork::MonadNine);
3644 monad_nine.clear_addresses();
3645 assert_eq!(
3646 monad_nine
3647 .labels
3648 .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
3649 .map(String::as_str),
3650 Some("ReserveBalance")
3651 );
3652 let after = monad_nine.decode_function(&trace).await;
3653 assert_eq!(
3654 after.call_data.as_ref().map(|call| call.signature.as_str()),
3655 Some("dippedIntoReserve()")
3656 );
3657 }
3658
3659 #[tokio::test]
3660 #[cfg(feature = "monad")]
3661 async fn test_monad_metadata_refreshes_across_hardforks() {
3662 let trace = CallTrace {
3663 address: monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
3664 data: dippedIntoReserveCall::SELECTOR.into(),
3665 output: true.abi_encode().into(),
3666 success: true,
3667 ..Default::default()
3668 };
3669 let mut decoder = monad_decoder(MonadHardfork::MonadEight);
3670
3671 decoder.set_hardfork(Some(MonadHardfork::MonadNine.into()));
3672 assert_eq!(decoder.hardfork(), Some(MonadHardfork::MonadNine.into()));
3673 assert_eq!(
3674 decoder
3675 .labels
3676 .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
3677 .map(String::as_str),
3678 Some("ReserveBalance")
3679 );
3680 let monad_nine = decoder.decode_function(&trace).await;
3681 assert_eq!(
3682 monad_nine.call_data.as_ref().map(|call| call.signature.as_str()),
3683 Some("dippedIntoReserve()")
3684 );
3685
3686 decoder.set_hardfork(Some(MonadHardfork::MonadEight.into()));
3687 assert_eq!(decoder.hardfork(), Some(MonadHardfork::MonadEight.into()));
3688 assert!(
3689 !decoder
3690 .labels
3691 .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
3692 );
3693 assert_eq!(
3694 decoder.labels.get(&monad_revm::staking::STAKING_ADDRESS).map(String::as_str),
3695 Some("Staking")
3696 );
3697 let monad_eight = decoder.decode_function(&trace).await;
3698 assert_ne!(
3699 monad_eight.call_data.as_ref().map(|call| call.signature.as_str()),
3700 Some("dippedIntoReserve()")
3701 );
3702 }
3703
3704 #[cfg(feature = "monad")]
3705 fn topic_from_u64(value: u64) -> B256 {
3706 let mut topic = [0u8; 32];
3707 topic[24..].copy_from_slice(&value.to_be_bytes());
3708 B256::from(topic)
3709 }
3710
3711 #[cfg(feature = "monad")]
3712 fn topic_from_address(address: Address) -> B256 {
3713 let mut topic = [0u8; 32];
3714 topic[12..].copy_from_slice(address.as_slice());
3715 B256::from(topic)
3716 }
3717
3718 #[tokio::test]
3719 async fn test_t7_storage_credits_call_and_error_decode() {
3720 let mut decoder = CallTraceDecoder::new().clone();
3721 decoder.chain_id = Some(4217);
3722
3723 let set_mode = IStorageCredits::setModeCall { newMode: IStorageCredits::Mode::Direct };
3725 let trace = CallTrace {
3726 address: STORAGE_CREDITS_ADDRESS,
3727 data: set_mode.abi_encode().into(),
3728 depth: 0,
3729 success: true,
3730 ..Default::default()
3731 };
3732 let decoded = decoder.decode_function(&trace).await;
3733 assert_eq!(decoded.label.as_deref(), Some("StorageCredits"));
3734 assert_eq!(decoded.call_data.expect("setMode should decode").signature, "setMode(uint8)");
3735
3736 let mode_of = IStorageCredits::modeOfCall { account: Address::repeat_byte(0x11) };
3738 let trace = CallTrace {
3739 address: STORAGE_CREDITS_ADDRESS,
3740 data: mode_of.abi_encode().into(),
3741 depth: 0,
3742 success: true,
3743 ..Default::default()
3744 };
3745 let decoded = decoder.decode_function(&trace).await;
3746 assert_eq!(decoded.call_data.expect("modeOf should decode").signature, "modeOf(address)");
3747
3748 let revert = decoder
3750 .revert_decoder
3751 .decode(IStorageCredits::InvalidMode {}.abi_encode().as_slice(), None);
3752 assert!(revert.contains("InvalidMode"), "{revert}");
3753
3754 let selector = IStorageCredits::balanceOfCall::SELECTOR;
3757 let funcs = decoder.functions.get(&selector).expect("balanceOf selector is registered");
3758 assert!(
3759 funcs.iter().any(|f| f.outputs.first().is_some_and(|o| o.ty == "uint256")),
3760 "global balanceOf must return uint256"
3761 );
3762 }
3763
3764 struct RecordingIdentifier {
3766 queried: Vec<Address>,
3767 }
3768 impl TraceIdentifier for RecordingIdentifier {
3769 fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec<IdentifiedAddress<'_>> {
3770 self.queried.extend(nodes.iter().map(|n| n.trace.address));
3771 Vec::new()
3772 }
3773 }
3774
3775 struct AbiIdentifier {
3776 abi: JsonAbi,
3777 }
3778
3779 impl TraceIdentifier for AbiIdentifier {
3780 fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec<IdentifiedAddress<'_>> {
3781 nodes
3782 .iter()
3783 .map(|node| IdentifiedAddress {
3784 address: node.trace.address,
3785 label: Some("Scoped".to_string()),
3786 contract: Some("Scoped".to_string()),
3787 abi: Some(Cow::Borrowed(&self.abi)),
3788 constructor_args_offset: None,
3789 artifact_id: None,
3790 })
3791 .collect()
3792 }
3793 }
3794
3795 #[test]
3796 fn scoped_identification_reuses_global_abi() {
3797 let abi = JsonAbi::parse(["function scoped(uint256)"]).unwrap();
3798 let function = abi.functions().next().unwrap().clone();
3799 let selector = function.selector();
3800 let mut decoder = CallTraceDecoderBuilder::new().with_abi(&abi).build();
3801 let global_functions = decoder.functions.get(&selector).unwrap().len();
3802
3803 let address = Address::repeat_byte(0x42);
3804 let mut arena = CallTraceArena::default();
3805 arena.nodes_mut()[0].trace.address = address;
3806 decoder.identify_scoped(&arena, &mut AbiIdentifier { abi });
3807
3808 assert_eq!(decoder.functions.get(&selector).unwrap().len(), global_functions);
3809 assert_eq!(decoder.functions_by_address[&address][&selector], [function]);
3810 }
3811
3812 #[tokio::test]
3813 async fn inactive_p256_address_uses_contract_identity() {
3814 let abi = JsonAbi::parse(["function ordinaryCode()"]).unwrap();
3815 let function = abi.functions().next().unwrap();
3816 let mut arena = CallTraceArena::default();
3817 arena.nodes_mut()[0].trace = CallTrace {
3818 address: P256_VERIFY,
3819 maybe_precompile: Some(false),
3820 data: function.selector().into(),
3821 success: true,
3822 ..Default::default()
3823 };
3824
3825 let mut decoder = CallTraceDecoder::new().clone();
3826 decoder.identify(&arena, &mut AbiIdentifier { abi });
3827 let decoded = decoder.decode_function(&arena.nodes()[0].trace).await;
3828
3829 assert_eq!(decoded.label.as_deref(), Some("Scoped"));
3830 assert_eq!(decoded.call_data.unwrap().signature, "ordinaryCode()");
3831 }
3832
3833 #[test]
3834 fn p256_is_not_an_unconditional_precompile_label() {
3835 assert!(!CallTraceDecoder::new().precompile_labels().contains_key(&P256_VERIFY));
3836 }
3837
3838 #[test]
3839 fn test_identify_addresses_skips_evm_precompiles() {
3840 use foundry_evm_core::precompiles::SHA_256;
3841
3842 let decoder = CallTraceDecoder::new();
3843
3844 let mut arena = CallTraceArena::default();
3845 let regular_addr = Address::repeat_byte(0x42);
3846 arena.nodes_mut()[0].trace.address = regular_addr;
3847
3848 arena.nodes_mut().push(CallTraceNode {
3850 trace: CallTrace {
3851 address: SHA_256,
3852 depth: 1,
3853 maybe_precompile: Some(true),
3854 ..Default::default()
3855 },
3856 idx: 1,
3857 ..Default::default()
3858 });
3859
3860 arena.nodes_mut().push(CallTraceNode {
3862 trace: CallTrace {
3863 address: SHA_256,
3864 depth: 1,
3865 maybe_precompile: None,
3866 ..Default::default()
3867 },
3868 idx: 2,
3869 ..Default::default()
3870 });
3871
3872 let mut identifier = RecordingIdentifier { queried: Vec::new() };
3873 decoder.identify_addresses(&arena, &mut identifier);
3874
3875 assert_eq!(identifier.queried, vec![regular_addr]);
3876 }
3877
3878 #[test]
3879 fn test_identify_addresses_skips_tempo_precompiles() {
3880 use foundry_evm_core::tempo::{TEMPO_PRECOMPILE_ADDRESSES, TIP20_CHANNEL_RESERVE_ADDRESS};
3881
3882 let decoder = CallTraceDecoderBuilder::new()
3884 .with_chain_id(Some(4217))
3885 .with_hardfork(Some(TempoHardfork::T5.into()))
3886 .build();
3887
3888 assert_eq!(
3889 decoder.labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS),
3890 Some(&"TIP20ChannelReserve".to_string())
3891 );
3892
3893 let mut arena = CallTraceArena::default();
3894 let regular_addr = Address::repeat_byte(0x42);
3895 arena.nodes_mut()[0].trace.address = regular_addr;
3896
3897 let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0];
3900 arena.nodes_mut().push(CallTraceNode {
3901 trace: CallTrace {
3902 address: tempo_precompile,
3903 depth: 1,
3904 maybe_precompile: None,
3905 ..Default::default()
3906 },
3907 idx: 1,
3908 ..Default::default()
3909 });
3910
3911 let mut identifier = RecordingIdentifier { queried: Vec::new() };
3912 decoder.identify_addresses(&arena, &mut identifier);
3913
3914 assert_eq!(identifier.queried, vec![regular_addr]);
3916 }
3917
3918 #[test]
3919 fn test_precompile_labels_follow_tempo_hardfork_activation_boundaries() {
3920 let labels_for_hardfork = |hardfork: TempoHardfork| {
3921 CallTraceDecoderBuilder::new()
3922 .with_hardfork(Some(hardfork.into()))
3923 .build()
3924 .precompile_labels()
3925 };
3926
3927 let t4_labels = labels_for_hardfork(TempoHardfork::T4);
3928 assert_eq!(t4_labels.get(&TIP_FEE_MANAGER_ADDRESS), Some(&"FeeManager".to_string()));
3929 assert!(!t4_labels.contains_key(&TIP20_CHANNEL_RESERVE_ADDRESS));
3930 assert!(!t4_labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
3931 assert!(!t4_labels.contains_key(&STORAGE_CREDITS_ADDRESS));
3932
3933 let t5_labels = labels_for_hardfork(TempoHardfork::T5);
3934 assert_eq!(t5_labels.get(&TIP_FEE_MANAGER_ADDRESS), Some(&"FeeManager".to_string()));
3935 assert_eq!(
3936 t5_labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS),
3937 Some(&"TIP20ChannelReserve".to_string())
3938 );
3939 assert!(!t5_labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
3940 assert!(!t5_labels.contains_key(&STORAGE_CREDITS_ADDRESS));
3941
3942 let t6_labels = labels_for_hardfork(TempoHardfork::T6);
3943 assert_eq!(
3944 t6_labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS),
3945 Some(&"TIP20ChannelReserve".to_string())
3946 );
3947 assert_eq!(
3948 t6_labels.get(&RECEIVE_POLICY_GUARD_ADDRESS),
3949 Some(&"ReceivePolicyGuard".to_string())
3950 );
3951 assert!(!t6_labels.contains_key(&STORAGE_CREDITS_ADDRESS));
3952
3953 let t7_labels = labels_for_hardfork(TempoHardfork::T7);
3954 assert_eq!(
3955 t7_labels.get(&RECEIVE_POLICY_GUARD_ADDRESS),
3956 Some(&"ReceivePolicyGuard".to_string())
3957 );
3958 assert_eq!(t7_labels.get(&STORAGE_CREDITS_ADDRESS), Some(&"StorageCredits".to_string()));
3959
3960 let ethereum_labels = CallTraceDecoderBuilder::new()
3961 .with_execution_network(NetworkVariant::Ethereum)
3962 .with_chain_id(Some(4217))
3963 .with_hardfork(Some(TempoHardfork::T7.into()))
3964 .build()
3965 .precompile_labels();
3966 assert!(!ethereum_labels.contains_key(&TIP20_CHANNEL_RESERVE_ADDRESS));
3967 assert!(!ethereum_labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
3968 assert!(!ethereum_labels.contains_key(&STORAGE_CREDITS_ADDRESS));
3969 }
3970
3971 #[test]
3972 fn celo_profile_is_authoritative_for_custom_precompile_labels() {
3973 let mut custom_celo = CallTraceDecoderBuilder::new()
3974 .with_networks(NetworkConfigs::with_celo())
3975 .with_chain_id(Some(98_765_432))
3976 .build();
3977 assert_eq!(
3978 custom_celo.precompile_labels().get(&CELO_TRANSFER),
3979 Some(&CELO_TRANSFER_LABEL.to_string())
3980 );
3981 custom_celo.clear_addresses();
3982 assert_eq!(
3983 custom_celo.precompile_labels().get(&CELO_TRANSFER),
3984 Some(&CELO_TRANSFER_LABEL.to_string())
3985 );
3986
3987 let explicit_ethereum = CallTraceDecoderBuilder::new()
3988 .with_networks(NetworkConfigs::default())
3989 .with_chain_id(Some(42_220))
3990 .build();
3991 assert!(!explicit_ethereum.precompile_labels().contains_key(&CELO_TRANSFER));
3992
3993 let inferred_celo = CallTraceDecoderBuilder::new().with_chain_id(Some(42_220)).build();
3994 assert_eq!(
3995 inferred_celo.precompile_labels().get(&CELO_TRANSFER),
3996 Some(&CELO_TRANSFER_LABEL.to_string())
3997 );
3998 }
3999
4000 #[cfg(feature = "base")]
4001 #[test]
4002 fn test_precompile_labels_follow_base_upgrade_boundaries() {
4003 let labels_for_upgrade = |upgrade| {
4004 CallTraceDecoderBuilder::new()
4005 .with_chain_id(Some(8453))
4006 .with_hardfork(Some(FoundryHardfork::Base(upgrade)))
4007 .build()
4008 .precompile_labels()
4009 };
4010 let base_precompiles = NetworkConfigs::with_base().precompiles(None);
4011 let base_label_count = |labels: &AddressHashMap<String>| {
4012 base_precompiles.values().filter(|address| labels.contains_key(*address)).count()
4013 };
4014
4015 assert_eq!(base_label_count(&labels_for_upgrade(BaseUpgrade::Azul)), 0);
4016 assert_eq!(base_label_count(&labels_for_upgrade(BaseUpgrade::Beryl)), 3);
4017 assert_eq!(base_label_count(&labels_for_upgrade(BaseUpgrade::Cobalt)), 5);
4018 }
4019
4020 #[tokio::test]
4021 async fn test_current_committee_decoding_is_durable_and_context_gated() {
4022 let abi = ICurrentCommittee::abi::contract();
4023 let function = abi.functions.get("getCommitteeMembers").unwrap().first().unwrap();
4024 let output = function
4025 .abi_encode_output(&[
4026 DynSolValue::Uint(U256::from(7), 64),
4027 DynSolValue::Array(vec![DynSolValue::FixedBytes(B256::with_last_byte(1), 32)]),
4028 ])
4029 .unwrap();
4030 let trace = CallTrace {
4031 address: CURRENT_COMMITTEE_ADDRESS,
4032 data: function.selector().into(),
4033 output: output.into(),
4034 success: true,
4035 ..Default::default()
4036 };
4037
4038 let mut decoder = CallTraceDecoderBuilder::new()
4039 .with_chain_id(Some(4217))
4040 .with_hardfork(Some(TempoHardfork::T8.into()))
4041 .build();
4042 decoder.clear_addresses();
4043 let decoded = decoder.decode_function(&trace).await;
4044 assert_eq!(decoded.label.as_deref(), Some("CurrentCommittee"));
4045 assert_eq!(decoded.call_data.unwrap().signature, "getCommitteeMembers()");
4046 assert_eq!(
4047 decoded.return_data.unwrap(),
4048 "7, [0x0000000000000000000000000000000000000000000000000000000000000001]"
4049 );
4050
4051 for decoder in [
4052 CallTraceDecoderBuilder::new()
4053 .with_chain_id(Some(4217))
4054 .with_hardfork(Some(TempoHardfork::T7.into()))
4055 .build(),
4056 CallTraceDecoderBuilder::new()
4057 .with_chain_id(Some(1))
4058 .with_hardfork(Some(TempoHardfork::T8.into()))
4059 .build(),
4060 ] {
4061 let decoded = decoder.decode_function(&trace).await;
4062 assert_ne!(decoded.label.as_deref(), Some("CurrentCommittee"));
4063 assert!(decoded.call_data.is_none());
4064 }
4065 }
4066
4067 #[tokio::test]
4068 async fn test_current_committee_unauthorized_is_address_scoped() {
4069 let output = ICurrentCommittee::Unauthorized {}.abi_encode();
4070 let trace = CallTrace {
4071 address: CURRENT_COMMITTEE_ADDRESS,
4072 output: output.clone().into(),
4073 success: false,
4074 status: Some(InstructionResult::Revert),
4075 ..Default::default()
4076 };
4077 let decoder = CallTraceDecoderBuilder::new()
4078 .with_chain_id(Some(4217))
4079 .with_hardfork(Some(TempoHardfork::T8.into()))
4080 .build();
4081 assert_eq!(
4082 decoder.decode_function(&trace).await.return_data.as_deref(),
4083 Some("Unauthorized()")
4084 );
4085
4086 let unrelated = CallTrace { address: Address::ZERO, ..trace };
4087 let baseline = CallTraceDecoder::new().decode_function(&unrelated).await;
4088 assert_eq!(decoder.decode_function(&unrelated).await.return_data, baseline.return_data);
4089 }
4090
4091 #[test]
4092 fn test_precompile_labels_skip_tempo_precompiles_on_other_chains() {
4093 let decoder = CallTraceDecoderBuilder::new()
4094 .with_chain_id(Some(1))
4095 .with_hardfork(Some(TempoHardfork::T6.into()))
4096 .build();
4097
4098 let labels = decoder.precompile_labels();
4099 assert!(!labels.contains_key(&TIP_FEE_MANAGER_ADDRESS));
4100 assert!(!labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
4101 assert!(!labels.contains_key(&STORAGE_CREDITS_ADDRESS));
4102 }
4103
4104 #[test]
4105 fn test_tempo_hardfork_labels_do_not_clobber_user_labels() {
4106 let reserve_label = "UserReserve".to_string();
4107 let guard_label = "UserGuard".to_string();
4108 let decoder = CallTraceDecoderBuilder::new()
4109 .with_labels([
4110 (TIP20_CHANNEL_RESERVE_ADDRESS, reserve_label.clone()),
4111 (RECEIVE_POLICY_GUARD_ADDRESS, guard_label.clone()),
4112 ])
4113 .with_hardfork(Some(TempoHardfork::T6.into()))
4114 .build();
4115
4116 assert_eq!(decoder.labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS), Some(&reserve_label));
4117 assert_eq!(decoder.labels.get(&RECEIVE_POLICY_GUARD_ADDRESS), Some(&guard_label));
4118 }
4119
4120 #[test]
4121 fn test_tempo_hardfork_transitions_rebuild_address_metadata() {
4122 let user_address = address!("0000000000000000000000000000000000001234");
4123 let user_label = "UserLabel".to_string();
4124 let mut decoder = CallTraceDecoderBuilder::new()
4125 .with_execution_network(NetworkVariant::Tempo)
4126 .with_labels([(user_address, user_label.clone())])
4127 .with_hardfork(Some(TempoHardfork::T4.into()))
4128 .build();
4129
4130 let labels = decoder.precompile_labels();
4131 assert!(labels.contains_key(&TIP_FEE_MANAGER_ADDRESS));
4132 assert!(!labels.contains_key(&TIP20_CHANNEL_RESERVE_ADDRESS));
4133 assert!(!labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
4134
4135 decoder.set_hardfork(Some(TempoHardfork::T6.into()));
4136 let labels = decoder.precompile_labels();
4137 assert!(labels.contains_key(&TIP_FEE_MANAGER_ADDRESS));
4138 assert!(labels.contains_key(&TIP20_CHANNEL_RESERVE_ADDRESS));
4139 assert!(labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
4140 assert_eq!(decoder.labels.get(&user_address), Some(&user_label));
4141
4142 decoder.set_hardfork(Some(TempoHardfork::T4.into()));
4143 let labels = decoder.precompile_labels();
4144 assert!(labels.contains_key(&TIP_FEE_MANAGER_ADDRESS));
4145 assert!(!labels.contains_key(&TIP20_CHANNEL_RESERVE_ADDRESS));
4146 assert!(!labels.contains_key(&RECEIVE_POLICY_GUARD_ADDRESS));
4147 assert_eq!(decoder.labels.get(&user_address), Some(&user_label));
4148
4149 decoder.set_hardfork(None);
4150 assert_eq!(decoder.labels.get(&user_address), Some(&user_label));
4151 }
4152
4153 #[test]
4154 fn test_tempo_hardfork_none_does_not_remove_user_reserve_label() {
4155 use foundry_evm_core::tempo::TIP20_CHANNEL_RESERVE_ADDRESS;
4156
4157 let reserve_label = "UserReserve".to_string();
4158 let decoder = CallTraceDecoderBuilder::new()
4159 .with_labels([(TIP20_CHANNEL_RESERVE_ADDRESS, reserve_label.clone())])
4160 .with_hardfork(None)
4161 .build();
4162
4163 assert_eq!(decoder.labels.get(&TIP20_CHANNEL_RESERVE_ADDRESS), Some(&reserve_label));
4164 }
4165
4166 #[tokio::test]
4167 async fn test_labels_validator_config_v2() {
4168 let decoder = CallTraceDecoderBuilder::new()
4169 .with_execution_network(NetworkVariant::Tempo)
4170 .with_hardfork(Some(TempoHardfork::T8.into()))
4171 .build();
4172 let trace = CallTrace { address: VALIDATOR_CONFIG_V2_ADDRESS, ..Default::default() };
4173
4174 assert_eq!(
4175 decoder.decode_function(&trace).await.label.as_deref(),
4176 Some("ValidatorConfigV2")
4177 );
4178 assert_eq!(
4179 decoder.precompile_labels().get(&VALIDATOR_CONFIG_V2_ADDRESS).map(String::as_str),
4180 Some("ValidatorConfigV2")
4181 );
4182 }
4183
4184 #[tokio::test]
4185 async fn test_decode_receive_policy_guard_at_t6() {
4186 let function = Function::parse("claim(address,bytes)").unwrap();
4187 let data = function
4188 .abi_encode_input(&[
4189 DynSolValue::Address(Address::repeat_byte(0x11)),
4190 DynSolValue::Bytes(vec![0x12, 0x34]),
4191 ])
4192 .unwrap();
4193 let trace = CallTrace {
4194 address: RECEIVE_POLICY_GUARD_ADDRESS,
4195 data: data.into(),
4196 success: true,
4197 ..Default::default()
4198 };
4199
4200 let decoder = CallTraceDecoderBuilder::new()
4201 .with_chain_id(Some(4217))
4202 .with_hardfork(Some(TempoHardfork::T6.into()))
4203 .build();
4204 let decoded = decoder.decode_function(&trace).await;
4205
4206 assert_eq!(decoded.label, Some("ReceivePolicyGuard".to_string()));
4207 assert_eq!(decoded.call_data.unwrap().signature, "claim(address,bytes)");
4208 }
4209
4210 #[tokio::test]
4211 async fn test_t6_receive_policy_calls_decode() {
4212 let decoder = CallTraceDecoderBuilder::new()
4213 .with_chain_id(Some(4217))
4214 .with_hardfork(Some(TempoHardfork::T6.into()))
4215 .build();
4216
4217 let set_policy = ITIP403Registry::setReceivePolicyCall {
4218 senderPolicyId: 7,
4219 tokenFilterId: 9,
4220 recoveryAuthority: address!("0x0000000000000000000000000000000000000abc"),
4221 };
4222 let decoded = decoder
4223 .decode_function(&CallTrace {
4224 address: TIP403_REGISTRY_ADDRESS,
4225 data: set_policy.abi_encode().into(),
4226 success: true,
4227 ..Default::default()
4228 })
4229 .await;
4230 let call_data = decoded.call_data.expect("setReceivePolicy should decode");
4231 assert_eq!(decoded.label.as_deref(), Some("TIP403Registry"));
4232 assert_eq!(call_data.signature, "setReceivePolicy(uint64,uint64,address)");
4233 assert_eq!(call_data.args[0], "7");
4234 assert_eq!(call_data.args[1], "9");
4235
4236 let validate = ITIP403Registry::validateReceivePolicyCall {
4237 token: PATH_USD_ADDRESS,
4238 sender: address!("0x0000000000000000000000000000000000000def"),
4239 receiver: address!("0x0000000000000000000000000000000000000123"),
4240 };
4241 let decoded = decoder
4242 .decode_function(&CallTrace {
4243 address: TIP403_REGISTRY_ADDRESS,
4244 data: validate.abi_encode().into(),
4245 success: true,
4246 ..Default::default()
4247 })
4248 .await;
4249 let call_data = decoded.call_data.expect("validateReceivePolicy should decode");
4250 assert_eq!(call_data.signature, "validateReceivePolicy(address,address,address)");
4251 assert!(call_data.args[0].contains("PathUSD"));
4252 }
4253
4254 #[tokio::test]
4255 async fn test_t6_admin_key_calls_decode() {
4256 let decoder = CallTraceDecoderBuilder::new()
4257 .with_chain_id(Some(4217))
4258 .with_hardfork(Some(TempoHardfork::T6.into()))
4259 .build();
4260 let account = address!("0x0000000000000000000000000000000000000abc");
4261 let key = address!("0x0000000000000000000000000000000000000def");
4262 let signature = vec![0x04, 0xaa, 0xbb];
4263
4264 let cases = [
4265 (
4266 ACCOUNT_KEYCHAIN_ADDRESS,
4267 IAccountKeychain::authorizeAdminKeyCall {
4268 keyId: key,
4269 signatureType: IAccountKeychain::SignatureType::Secp256k1,
4270 witness: B256::repeat_byte(0x11),
4271 }
4272 .abi_encode()
4273 .into(),
4274 "authorizeAdminKey(address,uint8,bytes32)",
4275 ),
4276 (
4277 ACCOUNT_KEYCHAIN_ADDRESS,
4278 IAccountKeychain::isAdminKeyCall { account, keyId: key }.abi_encode().into(),
4279 "isAdminKey(address,address)",
4280 ),
4281 (
4282 SIGNATURE_VERIFIER_ADDRESS,
4283 ISignatureVerifier::verifyKeychainCall {
4284 account,
4285 hash: B256::repeat_byte(0x22),
4286 signature: signature.clone().into(),
4287 }
4288 .abi_encode()
4289 .into(),
4290 "verifyKeychain(address,bytes32,bytes)",
4291 ),
4292 (
4293 SIGNATURE_VERIFIER_ADDRESS,
4294 ISignatureVerifier::verifyKeychainAdminCall {
4295 account,
4296 hash: B256::repeat_byte(0x33),
4297 signature: signature.into(),
4298 }
4299 .abi_encode()
4300 .into(),
4301 "verifyKeychainAdmin(address,bytes32,bytes)",
4302 ),
4303 ];
4304
4305 for (address, data, signature) in cases {
4306 let decoded = decoder
4307 .decode_function(&CallTrace { address, data, success: true, ..Default::default() })
4308 .await;
4309 assert_eq!(
4310 decoded.call_data.expect("T6 keychain call should decode").signature,
4311 signature
4312 );
4313 }
4314 }
4315
4316 #[tokio::test]
4317 async fn test_t6_receive_policy_and_admin_events_decode() {
4318 let decoder = CallTraceDecoder::new();
4319 let account = address!("0x0000000000000000000000000000000000000abc");
4320 let key = address!("0x0000000000000000000000000000000000000def");
4321
4322 let events = [
4323 (
4324 ITIP403Registry::ReceivePolicyUpdated {
4325 account,
4326 senderPolicyId: 7,
4327 tokenFilterId: 9,
4328 recoveryAuthority: key,
4329 }
4330 .encode_log_data(),
4331 "ReceivePolicyUpdated",
4332 ),
4333 (
4334 IAccountKeychain::AdminKeyAuthorized { account, publicKey: key }.encode_log_data(),
4335 "AdminKeyAuthorized",
4336 ),
4337 (
4338 IReceivePolicyGuard::ReceiptClaimed {
4339 token: PATH_USD_ADDRESS,
4340 receiver: account,
4341 blockedNonce: 11,
4342 blockedAt: 12,
4343 receiptVersion: 1,
4344 originator: key,
4345 recipient: account,
4346 recoveryAuthority: key,
4347 caller: key,
4348 to: account,
4349 amount: U256::from(123),
4350 }
4351 .encode_log_data(),
4352 "ReceiptClaimed",
4353 ),
4354 (
4355 IReceivePolicyGuard::ReceiptBurned {
4356 token: PATH_USD_ADDRESS,
4357 receiver: account,
4358 blockedNonce: 11,
4359 blockedAt: 12,
4360 receiptVersion: 1,
4361 originator: key,
4362 recipient: account,
4363 recoveryAuthority: key,
4364 caller: key,
4365 amount: U256::from(123),
4366 }
4367 .encode_log_data(),
4368 "ReceiptBurned",
4369 ),
4370 ];
4371
4372 for (log, expected_name) in events {
4373 let decoded = decoder.decode_event(&log).await;
4374 assert_eq!(decoded.name.as_deref(), Some(expected_name));
4375 assert!(decoded.params.expect("event params should decode").len() >= 2);
4376 }
4377 }
4378
4379 #[tokio::test]
4380 async fn test_t6_claim_receipt_bytes_decode_in_calls_and_transfer_blocked_event() {
4381 let decoder = CallTraceDecoder::new();
4382 let recovery = address!("0x0000000000000000000000000000000000000abc");
4383 let originator = address!("0x0000000000000000000000000000000000000def");
4384 let recipient = address!("0x0000000000000000000000000000000000000123");
4385 let receipt = IReceivePolicyGuard::ClaimReceiptV1::new(
4386 PATH_USD_ADDRESS,
4387 recovery,
4388 originator,
4389 recipient,
4390 12,
4391 34,
4392 ITIP403Registry::BlockedReason::RECEIVE_POLICY as u8,
4393 IReceivePolicyGuard::InboundKind::TRANSFER,
4394 B256::repeat_byte(0x44),
4395 )
4396 .abi_encode();
4397
4398 let claim =
4399 IReceivePolicyGuard::claimCall { to: recipient, receipt: receipt.clone().into() };
4400 let decoded = decoder
4401 .decode_function(&CallTrace {
4402 address: RECEIVE_POLICY_GUARD_ADDRESS,
4403 data: claim.abi_encode().into(),
4404 success: true,
4405 ..Default::default()
4406 })
4407 .await;
4408 let call_data = decoded.call_data.expect("claim should decode");
4409 assert_eq!(call_data.signature, "claim(address,bytes)");
4410 assert!(call_data.args[1].contains("ClaimReceiptV1"));
4411 assert!(call_data.args[1].contains("blockedNonce: 34"));
4412 assert!(call_data.args[1].contains(&originator.to_string()));
4413
4414 let decoded = decoder
4415 .decode_function(&CallTrace {
4416 address: Address::repeat_byte(0x77),
4417 data: claim.abi_encode().into(),
4418 success: true,
4419 ..Default::default()
4420 })
4421 .await;
4422 let call_data = decoded.call_data.expect("matching claim selector should decode");
4423 assert_eq!(call_data.signature, "claim(address,bytes)");
4424 assert!(!call_data.args[1].contains("ClaimReceiptV1"));
4425 assert!(call_data.args[1].starts_with("0x"));
4426
4427 let blocked = IReceivePolicyGuard::TransferBlocked {
4428 token: PATH_USD_ADDRESS,
4429 receiver: recipient,
4430 blockedNonce: 34,
4431 amount: U256::from(123),
4432 receiptVersion: 1,
4433 receipt: receipt.into(),
4434 };
4435 let blocked_log = blocked.encode_log_data();
4436 let decoded =
4437 decoder.decode_event_with_address(RECEIVE_POLICY_GUARD_ADDRESS, &blocked_log).await;
4438 assert_eq!(decoded.name.as_deref(), Some("TransferBlocked"));
4439 let params = decoded.params.expect("TransferBlocked params should decode");
4440 let receipt = params.iter().find(|(name, _)| name == "receipt").unwrap();
4441 assert!(receipt.1.contains("ClaimReceiptV1"));
4442 assert!(receipt.1.contains("blockedReason: 2"));
4443 assert!(receipt.1.contains("kind: 0"));
4444
4445 let decoded =
4446 decoder.decode_event_with_address(Address::repeat_byte(0x77), &blocked_log).await;
4447 assert_eq!(decoded.name.as_deref(), Some("TransferBlocked"));
4448 let params = decoded.params.expect("TransferBlocked params should decode");
4449 let receipt = params.iter().find(|(name, _)| name == "receipt").unwrap();
4450 assert!(!receipt.1.contains("ClaimReceiptV1"));
4451 assert!(receipt.1.starts_with("0x"));
4452 }
4453
4454 #[test]
4455 fn test_identify_addresses_does_not_skip_future_tempo_precompiles() {
4456 use foundry_evm_core::tempo::TIP20_CHANNEL_RESERVE_ADDRESS;
4457
4458 let decoder = CallTraceDecoderBuilder::new()
4459 .with_chain_id(Some(4217))
4460 .with_hardfork(Some(TempoHardfork::T4.into()))
4461 .build();
4462
4463 let mut arena = CallTraceArena::default();
4464 let regular_addr = Address::repeat_byte(0x42);
4465 arena.nodes_mut()[0].trace.address = regular_addr;
4466
4467 arena.nodes_mut().push(CallTraceNode {
4468 trace: CallTrace {
4469 address: TIP20_CHANNEL_RESERVE_ADDRESS,
4470 depth: 1,
4471 maybe_precompile: None,
4472 ..Default::default()
4473 },
4474 idx: 1,
4475 ..Default::default()
4476 });
4477
4478 let mut identifier = RecordingIdentifier { queried: Vec::new() };
4479 decoder.identify_addresses(&arena, &mut identifier);
4480
4481 assert_eq!(identifier.queried, vec![regular_addr, TIP20_CHANNEL_RESERVE_ADDRESS]);
4482 }
4483
4484 #[test]
4485 fn test_identify_addresses_does_not_skip_tempo_precompiles_on_other_chains() {
4486 use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES;
4487
4488 let mut decoder = CallTraceDecoder::new().clone();
4490 decoder.chain_id = Some(1);
4491
4492 let mut arena = CallTraceArena::default();
4493 let regular_addr = Address::repeat_byte(0x42);
4494 arena.nodes_mut()[0].trace.address = regular_addr;
4495
4496 let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0];
4497 arena.nodes_mut().push(CallTraceNode {
4498 trace: CallTrace {
4499 address: tempo_precompile,
4500 depth: 1,
4501 maybe_precompile: None,
4502 ..Default::default()
4503 },
4504 idx: 1,
4505 ..Default::default()
4506 });
4507
4508 let mut identifier = RecordingIdentifier { queried: Vec::new() };
4509 decoder.identify_addresses(&arena, &mut identifier);
4510
4511 assert_eq!(identifier.queried, vec![regular_addr, tempo_precompile]);
4513 }
4514
4515 #[test]
4516 #[cfg(feature = "monad")]
4517 fn test_identify_addresses_skips_monad_precompiles() {
4518 let decoder = monad_decoder(MonadHardfork::MonadNine);
4519
4520 let mut arena = CallTraceArena::default();
4521 let regular_addr = Address::repeat_byte(0x42);
4522 arena.nodes_mut()[0].trace.address = regular_addr;
4523
4524 arena.nodes_mut().push(CallTraceNode {
4525 trace: CallTrace {
4526 address: monad_revm::staking::STAKING_ADDRESS,
4527 depth: 1,
4528 maybe_precompile: None,
4529 ..Default::default()
4530 },
4531 idx: 1,
4532 ..Default::default()
4533 });
4534 arena.nodes_mut().push(CallTraceNode {
4535 trace: CallTrace {
4536 address: monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
4537 depth: 1,
4538 maybe_precompile: None,
4539 ..Default::default()
4540 },
4541 idx: 2,
4542 ..Default::default()
4543 });
4544
4545 let mut identifier = RecordingIdentifier { queried: Vec::new() };
4546 decoder.identify_addresses(&arena, &mut identifier);
4547
4548 assert_eq!(identifier.queried, vec![regular_addr]);
4549 }
4550
4551 #[test]
4552 #[cfg(feature = "monad")]
4553 fn test_identify_addresses_does_not_skip_monad_precompiles_on_other_chains() {
4554 let decoder = CallTraceDecoderBuilder::new().with_chain_id(Some(1)).build();
4555
4556 let mut arena = CallTraceArena::default();
4557 let regular_addr = Address::repeat_byte(0x42);
4558 arena.nodes_mut()[0].trace.address = regular_addr;
4559
4560 arena.nodes_mut().push(CallTraceNode {
4561 trace: CallTrace {
4562 address: monad_revm::staking::STAKING_ADDRESS,
4563 depth: 1,
4564 maybe_precompile: None,
4565 ..Default::default()
4566 },
4567 idx: 1,
4568 ..Default::default()
4569 });
4570 arena.nodes_mut().push(CallTraceNode {
4571 trace: CallTrace {
4572 address: monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
4573 depth: 1,
4574 maybe_precompile: None,
4575 ..Default::default()
4576 },
4577 idx: 2,
4578 ..Default::default()
4579 });
4580
4581 let mut identifier = RecordingIdentifier { queried: Vec::new() };
4582 decoder.identify_addresses(&arena, &mut identifier);
4583
4584 assert_eq!(
4585 identifier.queried,
4586 vec![
4587 regular_addr,
4588 monad_revm::staking::STAKING_ADDRESS,
4589 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
4590 ]
4591 );
4592 }
4593
4594 #[test]
4595 #[cfg(feature = "monad")]
4596 fn execution_network_overrides_nested_source_monad_precompile_detection() {
4597 assert!(!precompiles::is_known_precompile(
4598 monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS,
4599 Some(NetworkVariant::Ethereum.into()),
4600 Some(143),
4601 Some(MonadHardfork::MonadNine.into()),
4602 ));
4603 }
4604}