Skip to main content

foundry_evm_traces/decoder/
mod.rs

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/// A decoded event with both display-formatted parameters and their underlying ABI values.
62#[derive(Debug, Default)]
63pub struct DecodedEvent {
64    /// The event name or canonical signature.
65    pub name: Option<String>,
66    /// Event parameter names, display-formatted values, and decoded ABI values.
67    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/// Build a new [CallTraceDecoder].
82#[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    /// Create a new builder.
90    #[inline]
91    pub fn new() -> Self {
92        Self { decoder: CallTraceDecoder::new().clone() }
93    }
94
95    /// Add known labels to the decoder.
96    #[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    /// Add known errors to the decoder.
103    #[inline]
104    pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
105        self.decoder.collect_abi(abi, None, true);
106        self
107    }
108
109    /// Add events from an ABI for a specific contract address.
110    #[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    /// Add known contracts to the decoder.
119    #[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    /// Add known contracts to the decoder from a `LocalTraceIdentifier`.
129    #[inline]
130    pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
131        self.with_known_contracts(identifier.contracts())
132    }
133
134    /// Sets the verbosity level of the decoder.
135    #[inline]
136    pub const fn with_verbosity(mut self, level: u8) -> Self {
137        self.decoder.verbosity = level;
138        self
139    }
140
141    /// Applies trace rendering settings.
142    #[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    /// Sets the signature identifier for events and functions.
152    #[inline]
153    pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
154        self.decoder.signature_identifier = Some(identifier);
155        self
156    }
157
158    /// Sets the signature identifier for events and functions.
159    #[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    /// Sets the chain ID for network-specific precompile detection.
166    #[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    /// Sets the execution network used for network-specific precompile detection.
173    #[inline]
174    pub fn with_execution_network(self, network: NetworkVariant) -> Self {
175        self.with_networks(network.into())
176    }
177
178    /// Sets the complete execution profile used for network-specific precompile detection.
179    #[inline]
180    pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
181        self.decoder.networks = Some(networks);
182        self
183    }
184
185    /// Sets the Tempo hardfork for hardfork-specific precompile detection.
186    #[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    /// Sets the Monad hardfork used to register address-scoped metadata when built.
193    #[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    /// Hides addresses in trace parameters when a label is available.
201    #[inline]
202    pub const fn with_compact_labels(mut self, compact: bool) -> Self {
203        self.decoder.compact_labels = compact;
204        self
205    }
206
207    /// Sets the debug identifier for the decoder.
208    #[inline]
209    pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
210        self.decoder.debug_identifier = Some(identifier);
211        self
212    }
213
214    /// Build the decoder.
215    #[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/// The call trace decoder.
227///
228/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it
229/// then uses to decode the call trace.
230///
231/// Note that a call trace decoder is required for each new set of traces, since addresses in
232/// different sets might overlap.
233#[derive(Clone, Debug, Default)]
234pub struct CallTraceDecoder {
235    /// Addresses identified to be a specific contract.
236    ///
237    /// The values are in the form `"<artifact>:<contract>"`.
238    pub contracts: HashMap<Address, String>,
239    /// Address labels.
240    pub labels: HashMap<Address, String>,
241    /// Labels configured when the decoder was built.
242    base_labels: HashMap<Address, String>,
243    /// Contract addresses that have a receive function.
244    pub receive_contracts: HashSet<Address>,
245    /// Contract addresses that have fallback functions, mapped to function selectors of that
246    /// contract.
247    pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
248    /// Contract addresses that have do NOT have fallback functions, mapped to function selectors
249    /// of that contract.
250    pub non_fallback_contracts: HashMap<Address, HashSet<Selector>>,
251
252    /// All known functions.
253    pub functions: HashMap<Selector, Vec<Function>>,
254    /// Functions identified for a specific contract address.
255    pub functions_by_address: HashMap<Address, HashMap<Selector, Vec<Function>>>,
256    /// Constructors identified for a specific contract address.
257    pub constructors_by_address: HashMap<Address, Constructor>,
258    /// Creation input offsets where ABI-encoded constructor arguments begin.
259    pub constructor_args_offsets: HashMap<Address, usize>,
260    /// All known events.
261    ///
262    /// Key is: `(topics[0], topics.len() - 1)`.
263    pub events: BTreeMap<(B256, usize), Vec<Event>>,
264    /// Events identified for a specific contract address.
265    events_by_address: Option<Box<AddressEvents>>,
266    /// Anonymous events identified for a specific contract address, keyed by topic count.
267    anonymous_events_by_address: Option<Box<AddressAnonymousEvents>>,
268    /// Revert decoder. Contains all known custom errors.
269    pub revert_decoder: RevertDecoder,
270
271    /// A signature identifier for events and functions.
272    pub signature_identifier: Option<SignaturesIdentifier>,
273    /// Verbosity level
274    pub verbosity: u8,
275
276    /// Optional identifier of individual trace steps.
277    pub debug_identifier: Option<DebugTraceIdentifier>,
278
279    /// Disable showing of labels.
280    pub disable_labels: bool,
281
282    /// The chain ID, used to determine network-specific precompiles.
283    pub chain_id: Option<u64>,
284
285    /// Execution profile, kept separate from the source chain used for external identification.
286    networks: Option<NetworkConfigs>,
287
288    /// Detailed opcodes for analysis.
289    pub opcodes: Vec<OpCode>,
290
291    /// The Tempo hardfork, used to determine hardfork-specific precompiles.
292    pub tempo_hardfork: Option<TempoHardfork>,
293
294    /// The Monad hardfork, used to determine network- and hardfork-specific metadata.
295    monad_hardfork: Option<MonadHardfork>,
296
297    /// Hide addresses when a label is available, showing only the label.
298    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    /// Creates a new call trace decoder.
331    ///
332    /// The call trace decoder always knows how to decode calls to the cheatcode address, as well
333    /// as DSTest-style logs.
334    pub fn new() -> &'static Self {
335        // If you want to take arguments in this function, assign them to the fields of the cloned
336        // lazy instead of removing it
337        static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
338        INIT.get_or_init(Self::init)
339    }
340
341    /// Returns the Monad hardfork used for address-scoped metadata.
342    #[cfg(feature = "monad")]
343    pub const fn monad_hardfork(&self) -> Option<MonadHardfork> {
344        self.monad_hardfork
345    }
346
347    /// Returns the Tempo hardfork used for address-scoped metadata.
348    pub const fn tempo_hardfork(&self) -> Option<TempoHardfork> {
349        self.tempo_hardfork
350    }
351
352    /// Rebuilds address-scoped metadata for a new Tempo hardfork.
353    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    /// Rebuilds address-scoped metadata for a new Monad hardfork.
362    ///
363    /// Hardfork changes invalidate previously identified addresses because the set of active
364    /// precompiles can change. Global ABI and signature metadata is preserved.
365    #[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        // Materialized once so the revert decoder can take references below.
377        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            // Tempo
415            (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            // `IStorageCredits` shares the `balanceOf(address)` selector with `ITIP20`, so it
435            // must be chained first to keep `ITIP20`'s `uint256` return as the global fallback.
436            .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            // Decode Tempo precompile custom errors by name in traces.
491            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    /// Clears all known addresses.
513    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    /// Returns labels for precompiles active in this decoder's chain context.
538    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    /// Identify unknown addresses in the specified call trace using the specified identifier.
555    ///
556    /// Unknown contracts are contracts that either lack a label or an ABI.
557    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    /// Identifies unknown addresses without re-registering their ABIs globally.
562    ///
563    /// The identified ABIs must already be registered in this decoder.
564    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    /// Identify unknown addresses in the specified call trace using the specified identifier.
573    ///
574    /// Unknown contracts are contracts that either lack a label or an ABI.
575    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            // Skip precompile addresses, they will never resolve externally.
582            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    /// Adds a single event to the decoder.
600    pub fn push_event(&mut self, event: Event) {
601        self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
602    }
603
604    /// Adds a single event to the decoder for a specific contract address.
605    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    /// Adds a single function to the decoder.
632    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    /// Adds a single function to the decoder for a specific contract address.
644    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    /// Returns the functions registered for `selector` at `address`.
664    ///
665    /// Address-scoped metadata takes precedence over globally registered functions.
666    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    /// Selects the appropriate function from a list of functions with the same selector by
752    /// checking which one decodes the calldata.
753    ///
754    /// Address-scoped function lookup should happen before this to avoid using ABI metadata from a
755    /// different contract when multiple functions have the same input types.
756    fn select_contract_function<'a>(
757        &self,
758        functions: &'a [Function],
759        trace: &CallTrace,
760    ) -> &'a [Function] {
761        // When there are selector collisions, try to decode the calldata with each function
762        // to determine which one is actually being called. The correct function should
763        // decode successfully while the wrong ones will fail due to parameter type mismatches.
764        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    /// Adds a single error to the decoder.
777    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    /// Populates the traces with decoded data by mutating the
869    /// [CallTrace] in place. See [CallTraceDecoder::decode_function] and
870    /// [CallTraceDecoder::decode_event] for more details.
871    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    /// Decodes a call trace.
927    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            // Check if unsupported fn selector: calldata dooes NOT point to one of its selectors +
981            // non-fallback contract + no receive
982            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            // If traced contract is a fallback contract, check if it has the decoded function.
1027            // If not, then replace call data signature with `fallback`.
1028            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            // Calls to the cheatcode address with a known selector must not fall back to raw
1043            // calldata rendering: even malformed calldata can contain sensitive inputs like
1044            // private keys that the custom cheatcode decoding redacts.
1045            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    /// Decodes a function's input into the given trace.
1064    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                // Try to decode cheatcode inputs in a more custom way
1069                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    /// Decodes constructor input from a creation trace.
1099    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    /// Custom decoding for cheatcode inputs.
1122    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                // Redact private key in all cases
1149                Some(vec!["<pk>".to_string()])
1150            }
1151            "broadcast" | "startBroadcast" => {
1152                // Redact private key if defined
1153                // broadcast(uint256) / startBroadcast(uint256)
1154                (!func.inputs.is_empty() && func.inputs[0].ty == "uint256").then(|| vec!["<pk>".to_string()])
1155            }
1156            "getNonce" => {
1157                // Redact private key if defined
1158                // getNonce(Wallet)
1159                (!func.inputs.is_empty() && func.inputs[0].ty == "tuple").then(|| vec!["<pk>".to_string()])
1160            }
1161            "sign" | "signCompact" | "signP256" | "signKeychain" | "signKeychainAdmin" => {
1162                // Fail closed: when the arguments of a key-taking cheatcode cannot be
1163                // decoded, redact everything instead of deferring to generic rendering.
1164                let Ok(mut decoded) = func.abi_decode_input(&data[SELECTOR_LEN..]) else {
1165                    return Some(vec!["<redacted>".to_string()]);
1166                };
1167
1168                // Redact private key and replace in trace when the first parameter is a raw
1169                // private key (uint256) or a Wallet struct (tuple); digest-only and
1170                // signer-address overloads carry no key material and are left as-is.
1171                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                // Redact private key and nonce and replace in trace for
1184                // signWithNonceUnsafe(uint256 privateKey, bytes32 digest, uint256 nonce).
1185                // The nonce is the raw ECDSA k value: together with the digest and the
1186                // returned signature it allows recovering the private key.
1187                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                // Redact private key and replace in trace for
1196                // signEd25519(bytes namespace, bytes message, bytes32 privateKey)
1197                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                // Redact private key and replace in trace for
1205                // signAndAttachDelegation(address implementation, uint256 privateKey)
1206                // signDelegation(address implementation, uint256 privateKey)
1207                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` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
1227            "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                        // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
1243                        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                // Redact RPC URL except if referenced by an alias
1276                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    /// Decodes a function's output into the given trace.
1293    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            // Functions coming from an external database do not have any outputs specified,
1312            // and will lead to returning an empty list of values.
1313            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    /// Custom decoding for cheatcode outputs.
1326    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    /// The default decoded return data for a trace.
1355    async fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
1356        // For calls with status None or successful status, don't decode revert data
1357        // This is due to trace.status is derived from the revm_interpreter::InstructionResult in
1358        // revm-inspectors status will `None` post revm 27, as `InstructionResult::Continue` does
1359        // not exists anymore.
1360        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    /// Decodes revert data into a human-readable representation.
1367    ///
1368    /// If the revert decoder does not know the custom error, tries identifying the error selector
1369    /// with the signatures identifier, like unknown function and event signatures. This resolves
1370    /// errors from the local signatures cache populated by `forge build`, or from remote signature
1371    /// databases.
1372    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    /// Decodes an event.
1404    pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
1405        self.decode_event_inner(None, log, false).await.into_call_log()
1406    }
1407
1408    /// Decodes an event emitted by a known address.
1409    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    /// Decodes an event emitted by a known address, using its canonical signature as the name.
1418    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            // Topic zero does not encode indexed parameter placement, so global metadata is only
1446            // a hint. Try every compatible layout and reject ambiguity.
1447            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                        // Undo patched names.
1511                        let name = input.name.clone();
1512                        let display = self.format_param_value(
1513                            address,
1514                            &event.name,
1515                            &input.name,
1516                            &input.ty,
1517                            &param,
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    /// Prefetches function and event signatures into the identifier cache
1550    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                // Ignore known addresses.
1573                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                // Ignore non-ABI calldata.
1586                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                // Only consider reverted traces whose output the revert decoder cannot decode.
1597                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    /// Pretty-prints a value.
1612    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
1689/// Returns `true` if the given function calldata (including function selector) is ABI-encoded.
1690///
1691/// This is a simple heuristic to avoid fetching non ABI-encoded selectors.
1692fn 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
1700/// Returns `true` if the given data is ABI-encoded.
1701///
1702/// See [`is_abi_call_data`] for more details.
1703fn is_abi_data(data: &[u8]) -> bool {
1704    let rem = data.len() % 32;
1705    if rem == 0 || data.is_empty() {
1706        return true;
1707    }
1708    // If the length is not a multiple of 32, also accept when the last remainder bytes are all 0.
1709    data[data.len() - rem..].iter().all(|byte| *byte == 0)
1710}
1711
1712/// Restore the order of the params of a decoded event,
1713/// as Alloy returns the indexed and unindexed params separately.
1714fn 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        // Prevent panic of event `Transfer(from, to)` decoded with a signature
1720        // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making
1721        // sure the event inputs is not higher than decoded indexed / un-indexed values.
1722        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        // Create two functions with the same selector but different signatures
1889        let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
1890        let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
1891
1892        // Verify they have the same selector (this is the collision)
1893        assert_eq!(func1.selector(), func2.selector());
1894
1895        let functions = vec![func1, func2];
1896
1897        // Create a mock trace with calldata that matches func1
1898        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        // Should return only the function that can decode the calldata (func1)
1908        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        // Create two functions with the same selector but different signatures
1918        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        // Create a mock trace with calldata that matches func2
1924        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        // Should return only the function that can decode the calldata (func2)
1936        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        // [function_signature, data, expected]
2237        let cheatcode_input_test_cases = vec![
2238            // Should decode the expected revert payload, not full cheatcode calldata:
2239            (
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            // Should redact private key from traces in all cases:
2291            ("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            //
2303            // Should redact private key from traces in specific cases with exceptions:
2304            ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2305            ("broadcast()", vec![], None), // Ignore: `private key` is not passed.
2306            ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
2307            ("startBroadcast()", vec![], None), // Ignore: `private key` is not passed.
2308            ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
2309            ("getNonce(address)", vec![], None), // Ignore: `address` is public.
2310            //
2311            // Should redact private key and replace in trace in cases:
2312            (
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(Wallet,bytes32)
2362                "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                // Ignore: `private key` is not passed, digest is signed by the script signer.
2382                "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                // Ignore: `signer` is a public address.
2397                "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                // Labels are applied to address arguments while the key is redacted.
2468                "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                // cast calldata "signEd25519(bytes,bytes,bytes32)" "0x6e73" "0x6d7367"
2487                // 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
2488                "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                // cast calldata "createFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key"
2506                "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                // cast calldata "createFork(string)" "wss://eth-mainnet.g.alchemy.com/v2/api_key"
2521                "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                // cast calldata "createFork(string)" "mainnet"
2536                "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                // cast calldata "createFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1
2550                "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                // cast calldata "createFork(string,uint256)" "mainnet" 1
2566                "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                // cast calldata "createFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2581                "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                // cast calldata "createFork(string,bytes32)" "mainnet"
2601                // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2602                "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                // cast calldata "createSelectFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key"
2621                "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                // cast calldata "createSelectFork(string)" "mainnet"
2636                "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                // cast calldata "createSelectFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1
2650                "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                // cast calldata "createSelectFork(string,uint256)" "mainnet" 1
2666                "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                // cast calldata "createSelectFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2681                "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                // cast calldata "createSelectFork(string,bytes32)" "mainnet"
2701                // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
2702                "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                // cast calldata "rpc(string,string,string)" "https://eth-mainnet.g.alchemy.com/v2/api_key" "eth_getBalance" "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"
2721                "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                // cast calldata "rpc(string,string,string)" "mainnet" "eth_getBalance"
2748                // "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"
2749                "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        // [function_signature, expected]
2776        let cheatcode_output_test_cases = vec![
2777            // Should redact private key on output in all cases:
2778            ("createEd25519Key(bytes32)", Some("<pk>".to_string())),
2779            ("createWallet(string)", Some("<pk>".to_string())),
2780            ("deriveKey(string,uint32)", Some("<pk>".to_string())),
2781            // Should redact RPC URL if defined, except if referenced by an alias:
2782            ("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        // Well-formed sign(uint256,bytes32) resolves through selector lookup and redacts the
2813        // private key.
2814        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        // signEd25519 redacts the trailing private key argument.
2822        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        // Malformed calldata (nonzero trailing byte fails the ABI shape heuristic) must not
2834        // fall back to raw calldata rendering.
2835        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        // Truncated calldata cannot be decoded at all and fails closed.
2844        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        // A write call decodes to its signature and the precompile address is labeled.
3336        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        // A view call unique to this precompile also decodes.
3349        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        // The precompile's custom errors decode by name in reverts.
3361        let revert = decoder
3362            .revert_decoder
3363            .decode(IStorageCredits::InvalidMode {}.abi_encode().as_slice(), None);
3364        assert!(revert.contains("InvalidMode"), "{revert}");
3365
3366        // `balanceOf(address)` collides with `ITIP20`'s selector; the global map must keep
3367        // `ITIP20`'s `uint256` return so ordinary token balances above `u64::MAX` still decode.
3368        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    // A mock identifier that records which addresses it was asked to identify.
3377    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        // Standard EVM precompile flagged by the inspector.
3461        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        // Standard EVM precompile NOT flagged, caught by is_known_precompile.
3473        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        // Decoder with Tempo chain ID (4217).
3495        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        // Tempo precompile — not flagged by inspector, caught by is_known_precompile
3510        // only when chain_id is a Tempo chain.
3511        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        // On a Tempo chain, the Tempo precompile should be filtered out.
3527        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        // Decoder with Ethereum mainnet chain ID (1).
4063        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        // On Ethereum, Tempo precompile addresses are regular contracts — should NOT be filtered.
4086        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}