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