Skip to main content

foundry_evm_traces/decoder/
mod.rs

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