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