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