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