Skip to main content

foundry_evm_traces/decoder/
mod.rs

1use crate::{
2    CallTrace, CallTraceArena, CallTraceNode, DecodedCallData,
3    debug::DebugTraceIdentifier,
4    identifier::{IdentifiedAddress, LocalTraceIdentifier, SignaturesIdentifier, TraceIdentifier},
5};
6use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt};
7use alloy_json_abi::{Error, Event, Function, JsonAbi};
8use alloy_primitives::{
9    Address, B256, LogData, Selector,
10    map::{HashMap, HashSet, hash_map::Entry},
11};
12use foundry_common::{
13    ContractsByArtifact, SELECTOR_LEN, abi::get_indexed_event, fmt::format_token,
14    get_contract_name, selectors::SelectorKind,
15};
16use foundry_evm_core::{
17    abi::{Vm, console},
18    constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS},
19    decode::RevertDecoder,
20    precompiles::{
21        BLAKE_2F, BLS12_G1ADD, BLS12_G1MSM, BLS12_G2ADD, BLS12_G2MSM, BLS12_MAP_FP_TO_G1,
22        BLS12_MAP_FP2_TO_G2, BLS12_PAIRING_CHECK, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY,
23        MOD_EXP, P256_VERIFY, POINT_EVALUATION, RIPEMD_160, SHA_256,
24    },
25};
26use itertools::Itertools;
27use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace};
28use std::{collections::BTreeMap, sync::OnceLock};
29use tempo_contracts::precompiles::{
30    IAccountKeychain, IFeeManager, IStablecoinDEX, ITIP20Factory, ITIP403Registry, IValidatorConfig,
31};
32use tempo_precompiles::{
33    ACCOUNT_KEYCHAIN_ADDRESS, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS, STABLECOIN_DEX_ADDRESS,
34    TIP_FEE_MANAGER_ADDRESS, TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS,
35    VALIDATOR_CONFIG_ADDRESS, nonce::INonce, tip20::ITIP20,
36};
37
38mod precompiles;
39
40/// Build a new [CallTraceDecoder].
41#[derive(Default)]
42#[must_use = "builders do nothing unless you call `build` on them"]
43pub struct CallTraceDecoderBuilder {
44    decoder: CallTraceDecoder,
45}
46
47impl CallTraceDecoderBuilder {
48    /// Create a new builder.
49    #[inline]
50    pub fn new() -> Self {
51        Self { decoder: CallTraceDecoder::new().clone() }
52    }
53
54    /// Add known labels to the decoder.
55    #[inline]
56    pub fn with_labels(mut self, labels: impl IntoIterator<Item = (Address, String)>) -> Self {
57        self.decoder.labels.extend(labels);
58        self
59    }
60
61    /// Add known errors to the decoder.
62    #[inline]
63    pub fn with_abi(mut self, abi: &JsonAbi) -> Self {
64        self.decoder.collect_abi(abi, None);
65        self
66    }
67
68    /// Add known contracts to the decoder.
69    #[inline]
70    pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self {
71        trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs");
72        for contract in contracts.values() {
73            self.decoder.collect_abi(&contract.abi, None);
74        }
75        self
76    }
77
78    /// Add known contracts to the decoder from a `LocalTraceIdentifier`.
79    #[inline]
80    pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self {
81        self.with_known_contracts(identifier.contracts())
82    }
83
84    /// Sets the verbosity level of the decoder.
85    #[inline]
86    pub const fn with_verbosity(mut self, level: u8) -> Self {
87        self.decoder.verbosity = level;
88        self
89    }
90
91    /// Sets the signature identifier for events and functions.
92    #[inline]
93    pub fn with_signature_identifier(mut self, identifier: SignaturesIdentifier) -> Self {
94        self.decoder.signature_identifier = Some(identifier);
95        self
96    }
97
98    /// Sets the signature identifier for events and functions.
99    #[inline]
100    pub const fn with_label_disabled(mut self, disable_alias: bool) -> Self {
101        self.decoder.disable_labels = disable_alias;
102        self
103    }
104
105    /// Sets the chain ID for network-specific precompile detection.
106    #[inline]
107    pub const fn with_chain_id(mut self, chain_id: Option<u64>) -> Self {
108        self.decoder.chain_id = chain_id;
109        self
110    }
111
112    /// Sets the debug identifier for the decoder.
113    #[inline]
114    pub fn with_debug_identifier(mut self, identifier: DebugTraceIdentifier) -> Self {
115        self.decoder.debug_identifier = Some(identifier);
116        self
117    }
118
119    /// Build the decoder.
120    #[inline]
121    pub fn build(self) -> CallTraceDecoder {
122        self.decoder
123    }
124}
125
126/// The call trace decoder.
127///
128/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it
129/// then uses to decode the call trace.
130///
131/// Note that a call trace decoder is required for each new set of traces, since addresses in
132/// different sets might overlap.
133#[derive(Clone, Debug, Default)]
134pub struct CallTraceDecoder {
135    /// Addresses identified to be a specific contract.
136    ///
137    /// The values are in the form `"<artifact>:<contract>"`.
138    pub contracts: HashMap<Address, String>,
139    /// Address labels.
140    pub labels: HashMap<Address, String>,
141    /// Contract addresses that have a receive function.
142    pub receive_contracts: HashSet<Address>,
143    /// Contract addresses that have fallback functions, mapped to function selectors of that
144    /// contract.
145    pub fallback_contracts: HashMap<Address, HashSet<Selector>>,
146    /// Contract addresses that have do NOT have fallback functions, mapped to function selectors
147    /// of that contract.
148    pub non_fallback_contracts: HashMap<Address, HashSet<Selector>>,
149
150    /// All known functions.
151    pub functions: HashMap<Selector, Vec<Function>>,
152    /// All known events.
153    ///
154    /// Key is: `(topics[0], topics.len() - 1)`.
155    pub events: BTreeMap<(B256, usize), Vec<Event>>,
156    /// Revert decoder. Contains all known custom errors.
157    pub revert_decoder: RevertDecoder,
158
159    /// A signature identifier for events and functions.
160    pub signature_identifier: Option<SignaturesIdentifier>,
161    /// Verbosity level
162    pub verbosity: u8,
163
164    /// Optional identifier of individual trace steps.
165    pub debug_identifier: Option<DebugTraceIdentifier>,
166
167    /// Disable showing of labels.
168    pub disable_labels: bool,
169
170    /// The chain ID, used to determine network-specific precompiles.
171    pub chain_id: Option<u64>,
172}
173
174impl CallTraceDecoder {
175    /// Creates a new call trace decoder.
176    ///
177    /// The call trace decoder always knows how to decode calls to the cheatcode address, as well
178    /// as DSTest-style logs.
179    pub fn new() -> &'static Self {
180        // If you want to take arguments in this function, assign them to the fields of the cloned
181        // lazy instead of removing it
182        static INIT: OnceLock<CallTraceDecoder> = OnceLock::new();
183        INIT.get_or_init(Self::init)
184    }
185
186    #[instrument(name = "CallTraceDecoder::init", level = "debug")]
187    fn init() -> Self {
188        Self {
189            contracts: Default::default(),
190            labels: HashMap::from_iter([
191                (CHEATCODE_ADDRESS, "VM".to_string()),
192                (HARDHAT_CONSOLE_ADDRESS, "console".to_string()),
193                (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()),
194                (CALLER, "DefaultSender".to_string()),
195                (EC_RECOVER, "ECRecover".to_string()),
196                (SHA_256, "SHA-256".to_string()),
197                (RIPEMD_160, "RIPEMD-160".to_string()),
198                (IDENTITY, "Identity".to_string()),
199                (MOD_EXP, "ModExp".to_string()),
200                (EC_ADD, "ECAdd".to_string()),
201                (EC_MUL, "ECMul".to_string()),
202                (EC_PAIRING, "ECPairing".to_string()),
203                (BLAKE_2F, "Blake2F".to_string()),
204                (POINT_EVALUATION, "PointEvaluation".to_string()),
205                (BLS12_G1ADD, "BLS12_G1ADD".to_string()),
206                (BLS12_G1MSM, "BLS12_G1MSM".to_string()),
207                (BLS12_G2ADD, "BLS12_G2ADD".to_string()),
208                (BLS12_G2MSM, "BLS12_G2MSM".to_string()),
209                (BLS12_PAIRING_CHECK, "BLS12_PAIRING_CHECK".to_string()),
210                (BLS12_MAP_FP_TO_G1, "BLS12_MAP_FP_TO_G1".to_string()),
211                (BLS12_MAP_FP2_TO_G2, "BLS12_MAP_FP2_TO_G2".to_string()),
212                (P256_VERIFY, "P256VERIFY".to_string()),
213                // Tempo
214                (TIP_FEE_MANAGER_ADDRESS, "FeeManager".to_string()),
215                (TIP403_REGISTRY_ADDRESS, "TIP403Registry".to_string()),
216                (TIP20_FACTORY_ADDRESS, "TIP20Factory".to_string()),
217                (STABLECOIN_DEX_ADDRESS, "StablecoinDex".to_string()),
218                (NONCE_PRECOMPILE_ADDRESS, "Nonce".to_string()),
219                (VALIDATOR_CONFIG_ADDRESS, "ValidatorConfig".to_string()),
220                (ACCOUNT_KEYCHAIN_ADDRESS, "AccountKeychain".to_string()),
221                (PATH_USD_ADDRESS, "PathUSD".to_string()),
222            ]),
223            receive_contracts: Default::default(),
224            fallback_contracts: Default::default(),
225            non_fallback_contracts: Default::default(),
226
227            functions: console::hh::abi::functions()
228                .into_values()
229                .chain(Vm::abi::functions().into_values())
230                // Tempo
231                .chain(IFeeManager::abi::functions().into_values())
232                .chain(ITIP20::abi::functions().into_values())
233                .chain(ITIP403Registry::abi::functions().into_values())
234                .chain(ITIP20Factory::abi::functions().into_values())
235                .chain(IStablecoinDEX::abi::functions().into_values())
236                .chain(INonce::abi::functions().into_values())
237                .chain(IValidatorConfig::abi::functions().into_values())
238                .chain(IAccountKeychain::abi::functions().into_values())
239                .flatten()
240                .map(|func| (func.selector(), vec![func]))
241                .collect(),
242            events: console::ds::abi::events()
243                .into_values()
244                // Tempo
245                .chain(IFeeManager::abi::events().into_values())
246                .chain(ITIP20::abi::events().into_values())
247                .chain(ITIP403Registry::abi::events().into_values())
248                .chain(ITIP20Factory::abi::events().into_values())
249                .chain(IStablecoinDEX::abi::events().into_values())
250                .chain(INonce::abi::events().into_values())
251                .chain(IValidatorConfig::abi::events().into_values())
252                .chain(IAccountKeychain::abi::events().into_values())
253                .flatten()
254                .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event]))
255                .collect(),
256            revert_decoder: Default::default(),
257
258            signature_identifier: None,
259            verbosity: 0,
260
261            debug_identifier: None,
262
263            disable_labels: false,
264
265            chain_id: None,
266        }
267    }
268
269    /// Clears all known addresses.
270    pub fn clear_addresses(&mut self) {
271        self.contracts.clear();
272
273        let default_labels = &Self::new().labels;
274        if self.labels.len() > default_labels.len() {
275            self.labels.clone_from(default_labels);
276        }
277
278        self.receive_contracts.clear();
279        self.fallback_contracts.clear();
280        self.non_fallback_contracts.clear();
281    }
282
283    /// Identify unknown addresses in the specified call trace using the specified identifier.
284    ///
285    /// Unknown contracts are contracts that either lack a label or an ABI.
286    pub fn identify(&mut self, arena: &CallTraceArena, identifier: &mut impl TraceIdentifier) {
287        self.collect_identified_addresses(self.identify_addresses(arena, identifier));
288    }
289
290    /// Identify unknown addresses in the specified call trace using the specified identifier.
291    ///
292    /// Unknown contracts are contracts that either lack a label or an ABI.
293    pub fn identify_addresses<'a>(
294        &self,
295        arena: &CallTraceArena,
296        identifier: &'a mut impl TraceIdentifier,
297    ) -> Vec<IdentifiedAddress<'a>> {
298        let nodes = arena.nodes().iter().filter(|node| {
299            // Skip precompile addresses, they will never resolve externally.
300            if node.is_precompile()
301                || precompiles::is_known_precompile(node.trace.address, self.chain_id)
302            {
303                return false;
304            }
305            let address = &node.trace.address;
306            !self.labels.contains_key(address) || !self.contracts.contains_key(address)
307        });
308        identifier.identify_addresses(&nodes.collect::<Vec<_>>())
309    }
310
311    /// Adds a single event to the decoder.
312    pub fn push_event(&mut self, event: Event) {
313        self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event);
314    }
315
316    /// Adds a single function to the decoder.
317    pub fn push_function(&mut self, function: Function) {
318        match self.functions.entry(function.selector()) {
319            Entry::Occupied(entry) => {
320                // This shouldn't happen that often.
321                if entry.get().contains(&function) {
322                    return;
323                }
324                trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector");
325                entry.into_mut().push(function);
326            }
327            Entry::Vacant(entry) => {
328                entry.insert(vec![function]);
329            }
330        }
331    }
332
333    /// Selects the appropriate function from a list of functions with the same selector
334    /// by checking which one belongs to the contract being called, this avoids collisions
335    /// where multiple different functions across different contracts have the same selector.
336    fn select_contract_function<'a>(
337        &self,
338        functions: &'a [Function],
339        trace: &CallTrace,
340    ) -> &'a [Function] {
341        // When there are selector collisions, try to decode the calldata with each function
342        // to determine which one is actually being called. The correct function should
343        // decode successfully while the wrong ones will fail due to parameter type mismatches.
344        if functions.len() > 1 {
345            for (i, func) in functions.iter().enumerate() {
346                if trace.data.len() >= SELECTOR_LEN
347                    && func.abi_decode_input(&trace.data[SELECTOR_LEN..]).is_ok()
348                {
349                    return &functions[i..i + 1];
350                }
351            }
352        }
353        functions
354    }
355
356    /// Adds a single error to the decoder.
357    pub fn push_error(&mut self, error: Error) {
358        self.revert_decoder.push_error(error);
359    }
360
361    pub const fn without_label(&mut self, disable: bool) {
362        self.disable_labels = disable;
363    }
364
365    fn collect_identified_addresses(&mut self, mut addrs: Vec<IdentifiedAddress<'_>>) {
366        addrs.sort_by_key(|identity| identity.address);
367        addrs.dedup_by_key(|identity| identity.address);
368        if addrs.is_empty() {
369            return;
370        }
371
372        trace!(target: "evm::traces", len=addrs.len(), "collecting address identities");
373        for IdentifiedAddress { address, label, contract, abi, artifact_id: _ } in addrs {
374            let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered();
375
376            if let Some(contract) = contract {
377                self.contracts.entry(address).or_insert(contract);
378            }
379
380            if let Some(label) = label.filter(|s| !s.is_empty()) {
381                self.labels.entry(address).or_insert(label);
382            }
383
384            if let Some(abi) = abi {
385                self.collect_abi(&abi, Some(address));
386            }
387        }
388    }
389
390    fn collect_abi(&mut self, abi: &JsonAbi, address: Option<Address>) {
391        let len = abi.len();
392        if len == 0 {
393            return;
394        }
395        trace!(target: "evm::traces", len, ?address, "collecting ABI");
396        for function in abi.functions() {
397            self.push_function(function.clone());
398        }
399        for event in abi.events() {
400            self.push_event(event.clone());
401        }
402        for error in abi.errors() {
403            self.push_error(error.clone());
404        }
405        if let Some(address) = address {
406            if abi.receive.is_some() {
407                self.receive_contracts.insert(address);
408            }
409
410            if abi.fallback.is_some() {
411                self.fallback_contracts
412                    .insert(address, abi.functions().map(|f| f.selector()).collect());
413            } else {
414                self.non_fallback_contracts
415                    .insert(address, abi.functions().map(|f| f.selector()).collect());
416            }
417        }
418    }
419
420    /// Populates the traces with decoded data by mutating the
421    /// [CallTrace] in place. See [CallTraceDecoder::decode_function] and
422    /// [CallTraceDecoder::decode_event] for more details.
423    pub async fn populate_traces(&self, traces: &mut Vec<CallTraceNode>) {
424        for node in traces {
425            node.trace.decoded = Some(Box::new(self.decode_function(&node.trace).await));
426            for log in &mut node.logs {
427                log.decoded = Some(Box::new(self.decode_event(&log.raw_log).await));
428            }
429
430            if let Some(debug) = self.debug_identifier.as_ref()
431                && let Some(identified) = self.contracts.get(&node.trace.address)
432            {
433                debug.identify_node_steps(node, get_contract_name(identified))
434            }
435        }
436    }
437
438    /// Decodes a call trace.
439    pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace {
440        let label =
441            if self.disable_labels { None } else { self.labels.get(&trace.address).cloned() };
442
443        if trace.kind.is_any_create() {
444            return DecodedCallTrace { label, ..Default::default() };
445        }
446
447        if let Some(trace) = precompiles::decode(trace, self.chain_id) {
448            return trace;
449        }
450
451        let cdata = &trace.data;
452        if trace.address == DEFAULT_CREATE2_DEPLOYER {
453            return DecodedCallTrace {
454                label,
455                call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }),
456                return_data: self.default_return_data(trace),
457            };
458        }
459
460        if is_abi_call_data(cdata) {
461            let selector = Selector::try_from(&cdata[..SELECTOR_LEN]).unwrap();
462            let mut functions = Vec::new();
463            let functions = match self.functions.get(&selector) {
464                Some(fs) => fs,
465                None => {
466                    if let Some(identifier) = &self.signature_identifier
467                        && let Some(function) = identifier.identify_function(selector).await
468                    {
469                        functions.push(function);
470                    }
471                    &functions
472                }
473            };
474
475            // Check if unsupported fn selector: calldata dooes NOT point to one of its selectors +
476            // non-fallback contract + no receive
477            if let Some(contract_selectors) = self.non_fallback_contracts.get(&trace.address)
478                && !contract_selectors.contains(&selector)
479                && (!cdata.is_empty() || !self.receive_contracts.contains(&trace.address))
480            {
481                let return_data = if trace.success {
482                    None
483                } else {
484                    let revert_msg = self.revert_decoder.decode(&trace.output, trace.status);
485
486                    if trace.output.is_empty() || revert_msg.contains("EvmError: Revert") {
487                        Some(format!(
488                            "unrecognized function selector {} for contract {}, which has no fallback function.",
489                            selector, trace.address
490                        ))
491                    } else {
492                        Some(revert_msg)
493                    }
494                };
495
496                return if let Some(func) = functions.first() {
497                    DecodedCallTrace {
498                        label,
499                        call_data: Some(self.decode_function_input(trace, func)),
500                        return_data,
501                    }
502                } else {
503                    DecodedCallTrace {
504                        label,
505                        call_data: self.fallback_call_data(trace),
506                        return_data,
507                    }
508                };
509            }
510
511            let contract_functions = self.select_contract_function(functions, trace);
512            let [func, ..] = contract_functions else {
513                return DecodedCallTrace {
514                    label,
515                    call_data: self.fallback_call_data(trace),
516                    return_data: self.default_return_data(trace),
517                };
518            };
519
520            // If traced contract is a fallback contract, check if it has the decoded function.
521            // If not, then replace call data signature with `fallback`.
522            let mut call_data = self.decode_function_input(trace, func);
523            if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address)
524                && !fallback_functions.contains(&selector)
525                && let Some(cd) = self.fallback_call_data(trace)
526            {
527                call_data.signature = cd.signature;
528            }
529
530            DecodedCallTrace {
531                label,
532                call_data: Some(call_data),
533                return_data: self.decode_function_output(trace, contract_functions),
534            }
535        } else {
536            DecodedCallTrace {
537                label,
538                call_data: self.fallback_call_data(trace),
539                return_data: self.default_return_data(trace),
540            }
541        }
542    }
543
544    /// Decodes a function's input into the given trace.
545    fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData {
546        let mut args = None;
547        if trace.data.len() >= SELECTOR_LEN {
548            if trace.address == CHEATCODE_ADDRESS {
549                // Try to decode cheatcode inputs in a more custom way
550                if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) {
551                    args = Some(v);
552                }
553            }
554
555            if args.is_none()
556                && let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..])
557            {
558                args = Some(v.iter().map(|value| self.format_value(value)).collect());
559            }
560        }
561
562        DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() }
563    }
564
565    /// Custom decoding for cheatcode inputs.
566    fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option<Vec<String>> {
567        match func.name.as_str() {
568            "expectRevert" => {
569                let decoded = match data.get(SELECTOR_LEN..) {
570                    Some(data) => func.abi_decode_input(data).ok(),
571                    None => None,
572                };
573                let Some(decoded) = decoded else {
574                    return Some(vec![self.revert_decoder.decode(data, None)]);
575                };
576                let Some(first) = decoded.first() else {
577                    return Some(vec![self.revert_decoder.decode(data, None)]);
578                };
579                let expected_revert = match first {
580                    DynSolValue::Bytes(bytes) => bytes.as_slice(),
581                    DynSolValue::FixedBytes(word, size) => &word[..*size],
582                    _ => return None,
583                };
584                Some(
585                    std::iter::once(self.revert_decoder.decode(expected_revert, None))
586                        .chain(decoded.iter().skip(1).map(|value| self.format_value(value)))
587                        .collect(),
588                )
589            }
590            "addr" | "createWallet" | "deriveKey" | "rememberKey" => {
591                // Redact private key in all cases
592                Some(vec!["<pk>".to_string()])
593            }
594            "broadcast" | "startBroadcast" => {
595                // Redact private key if defined
596                // broadcast(uint256) / startBroadcast(uint256)
597                (!func.inputs.is_empty() && func.inputs[0].ty == "uint256").then(|| vec!["<pk>".to_string()])
598            }
599            "getNonce" => {
600                // Redact private key if defined
601                // getNonce(Wallet)
602                (!func.inputs.is_empty() && func.inputs[0].ty == "tuple").then(|| vec!["<pk>".to_string()])
603            }
604            "sign" | "signP256" => {
605                let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
606
607                // Redact private key and replace in trace
608                // sign(uint256,bytes32) / signP256(uint256,bytes32) / sign(Wallet,bytes32)
609                if !decoded.is_empty() &&
610                    (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple")
611                {
612                    decoded[0] = DynSolValue::String("<pk>".to_string());
613                }
614
615                Some(decoded.iter().map(format_token).collect())
616            }
617            "signDelegation" | "signAndAttachDelegation" => {
618                let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
619                // Redact private key and replace in trace for
620                // signAndAttachDelegation(address implementation, uint256 privateKey)
621                // signDelegation(address implementation, uint256 privateKey)
622                decoded[1] = DynSolValue::String("<pk>".to_string());
623                Some(decoded.iter().map(format_token).collect())
624            }
625            "parseJson" |
626            "parseJsonUint" |
627            "parseJsonUintArray" |
628            "parseJsonInt" |
629            "parseJsonIntArray" |
630            "parseJsonString" |
631            "parseJsonStringArray" |
632            "parseJsonAddress" |
633            "parseJsonAddressArray" |
634            "parseJsonBool" |
635            "parseJsonBoolArray" |
636            "parseJsonBytes" |
637            "parseJsonBytesArray" |
638            "parseJsonBytes32" |
639            "parseJsonBytes32Array" |
640            "writeJson" |
641            // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
642            "keyExists" |
643            "keyExistsJson" |
644            "serializeBool" |
645            "serializeUint" |
646            "serializeUintToHex" |
647            "serializeInt" |
648            "serializeAddress" |
649            "serializeBytes32" |
650            "serializeString" |
651            "serializeBytes" => {
652                if self.verbosity >= 5 {
653                    None
654                } else {
655                    let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
656                    let token = if func.name.as_str() == "parseJson" ||
657                        // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.
658                        func.name.as_str() == "keyExists" ||
659                        func.name.as_str() == "keyExistsJson"
660                    {
661                        "<JSON file>"
662                    } else {
663                        "<stringified JSON>"
664                    };
665                    decoded[0] = DynSolValue::String(token.to_string());
666                    Some(decoded.iter().map(format_token).collect())
667                }
668            }
669            s if s.contains("Toml") => {
670                if self.verbosity >= 5 {
671                    None
672                } else {
673                    let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
674                    let token = if func.name.as_str() == "parseToml" ||
675                        func.name.as_str() == "keyExistsToml"
676                    {
677                        "<TOML file>"
678                    } else {
679                        "<stringified TOML>"
680                    };
681                    decoded[0] = DynSolValue::String(token.to_string());
682                    Some(decoded.iter().map(format_token).collect())
683                }
684            }
685            "createFork" |
686            "createSelectFork" |
687            "rpc" => {
688                let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..]).ok()?;
689
690                // Redact RPC URL except if referenced by an alias
691                if !decoded.is_empty() && func.inputs[0].ty == "string" {
692                    let url_or_alias = decoded[0].as_str().unwrap_or_default();
693
694                    if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") {
695                        decoded[0] = DynSolValue::String("<rpc url>".to_string());
696                    }
697                } else {
698                    return None;
699                }
700
701                Some(decoded.iter().map(format_token).collect())
702            }
703            _ => None,
704        }
705    }
706
707    /// Decodes a function's output into the given trace.
708    fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option<String> {
709        if !trace.success {
710            return self.default_return_data(trace);
711        }
712
713        if trace.address == CHEATCODE_ADDRESS
714            && let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func))
715        {
716            return Some(decoded);
717        }
718
719        if let Some(values) =
720            funcs.iter().find_map(|func| func.abi_decode_output(&trace.output).ok())
721        {
722            // Functions coming from an external database do not have any outputs specified,
723            // and will lead to returning an empty list of values.
724            if values.is_empty() {
725                return None;
726            }
727
728            return Some(
729                values.iter().map(|value| self.format_value(value)).format(", ").to_string(),
730            );
731        }
732
733        None
734    }
735
736    /// Custom decoding for cheatcode outputs.
737    fn decode_cheatcode_outputs(&self, func: &Function) -> Option<String> {
738        match func.name.as_str() {
739            s if s.starts_with("env") => Some("<env var value>"),
740            "createWallet" | "deriveKey" => Some("<pk>"),
741            "promptSecret" | "promptSecretUint" => Some("<secret>"),
742            "parseJson" if self.verbosity < 5 => Some("<encoded JSON value>"),
743            "readFile" if self.verbosity < 5 => Some("<file>"),
744            "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some("<rpc url>"),
745            _ => None,
746        }
747        .map(Into::into)
748    }
749
750    #[track_caller]
751    fn fallback_call_data(&self, trace: &CallTrace) -> Option<DecodedCallData> {
752        let cdata = &trace.data;
753        let signature = if cdata.is_empty() && self.receive_contracts.contains(&trace.address) {
754            "receive()"
755        } else if self.fallback_contracts.contains_key(&trace.address) {
756            "fallback()"
757        } else {
758            return None;
759        }
760        .to_string();
761        let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
762        Some(DecodedCallData { signature, args })
763    }
764
765    /// The default decoded return data for a trace.
766    fn default_return_data(&self, trace: &CallTrace) -> Option<String> {
767        // For calls with status None or successful status, don't decode revert data
768        // This is due to trace.status is derived from the revm_interpreter::InstructionResult in
769        // revm-inspectors status will `None` post revm 27, as `InstructionResult::Continue` does
770        // not exists anymore.
771        if trace.status.is_none() || trace.status.is_some_and(|s| s.is_ok()) {
772            return None;
773        }
774        (!trace.success).then(|| self.revert_decoder.decode(&trace.output, trace.status))
775    }
776
777    /// Decodes an event.
778    pub async fn decode_event(&self, log: &LogData) -> DecodedCallLog {
779        let &[t0, ..] = log.topics() else { return DecodedCallLog { name: None, params: None } };
780
781        let mut events = Vec::new();
782        let events = match self.events.get(&(t0, log.topics().len() - 1)) {
783            Some(es) => es,
784            None => {
785                if let Some(identifier) = &self.signature_identifier
786                    && let Some(event) = identifier.identify_event(t0).await
787                {
788                    events.push(get_indexed_event(event, log));
789                }
790                &events
791            }
792        };
793        for event in events {
794            if let Ok(decoded) = event.decode_log(log) {
795                let params = reconstruct_params(event, &decoded);
796                return DecodedCallLog {
797                    name: Some(event.name.clone()),
798                    params: Some(
799                        params
800                            .into_iter()
801                            .zip(event.inputs.iter())
802                            .map(|(param, input)| {
803                                // undo patched names
804                                let name = input.name.clone();
805                                (name, self.format_value(&param))
806                            })
807                            .collect(),
808                    ),
809                };
810            }
811        }
812
813        DecodedCallLog { name: None, params: None }
814    }
815
816    /// Prefetches function and event signatures into the identifier cache
817    pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) {
818        let Some(identifier) = &self.signature_identifier else { return };
819        let events = nodes
820            .iter()
821            .flat_map(|node| {
822                node.logs
823                    .iter()
824                    .map(|log| log.raw_log.topics())
825                    .filter(|&topics| {
826                        if let Some(&first) = topics.first()
827                            && self.events.contains_key(&(first, topics.len() - 1))
828                        {
829                            return false;
830                        }
831                        true
832                    })
833                    .filter_map(|topics| topics.first())
834            })
835            .copied();
836        let functions = nodes
837            .iter()
838            .filter(|&n| {
839                // Ignore known addresses.
840                if n.trace.address == DEFAULT_CREATE2_DEPLOYER
841                    || n.is_precompile()
842                    || precompiles::is_known_precompile(n.trace.address, self.chain_id)
843                {
844                    return false;
845                }
846                // Ignore non-ABI calldata.
847                if n.trace.kind.is_any_create() || !is_abi_call_data(&n.trace.data) {
848                    return false;
849                }
850                true
851            })
852            .filter_map(|n| n.trace.data.first_chunk().map(Selector::from))
853            .filter(|selector| !self.functions.contains_key(selector));
854        let selectors = events
855            .map(SelectorKind::Event)
856            .chain(functions.map(SelectorKind::Function))
857            .unique()
858            .collect::<Vec<_>>();
859        let _ = identifier.identify(&selectors).await;
860    }
861
862    /// Pretty-prints a value.
863    fn format_value(&self, value: &DynSolValue) -> String {
864        if let DynSolValue::Address(addr) = value
865            && let Some(label) = self.labels.get(addr)
866        {
867            return format!("{label}: [{addr}]");
868        }
869        format_token(value)
870    }
871}
872
873/// Returns `true` if the given function calldata (including function selector) is ABI-encoded.
874///
875/// This is a simple heuristic to avoid fetching non ABI-encoded selectors.
876fn is_abi_call_data(data: &[u8]) -> bool {
877    match data.len().cmp(&SELECTOR_LEN) {
878        std::cmp::Ordering::Less => false,
879        std::cmp::Ordering::Equal => true,
880        std::cmp::Ordering::Greater => is_abi_data(&data[SELECTOR_LEN..]),
881    }
882}
883
884/// Returns `true` if the given data is ABI-encoded.
885///
886/// See [`is_abi_call_data`] for more details.
887fn is_abi_data(data: &[u8]) -> bool {
888    let rem = data.len() % 32;
889    if rem == 0 || data.is_empty() {
890        return true;
891    }
892    // If the length is not a multiple of 32, also accept when the last remainder bytes are all 0.
893    data[data.len() - rem..].iter().all(|byte| *byte == 0)
894}
895
896/// Restore the order of the params of a decoded event,
897/// as Alloy returns the indexed and unindexed params separately.
898fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec<DynSolValue> {
899    let mut indexed = 0;
900    let mut unindexed = 0;
901    let mut inputs = vec![];
902    for input in &event.inputs {
903        // Prevent panic of event `Transfer(from, to)` decoded with a signature
904        // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making
905        // sure the event inputs is not higher than decoded indexed / un-indexed values.
906        if input.indexed && indexed < decoded.indexed.len() {
907            inputs.push(decoded.indexed[indexed].clone());
908            indexed += 1;
909        } else if unindexed < decoded.body.len() {
910            inputs.push(decoded.body[unindexed].clone());
911            unindexed += 1;
912        }
913    }
914
915    inputs
916}
917
918fn indexed_inputs(event: &Event) -> usize {
919    event.inputs.iter().filter(|param| param.indexed).count()
920}
921
922#[cfg(test)]
923mod tests {
924    use super::*;
925    use alloy_primitives::hex;
926
927    #[test]
928    fn test_selector_collision_resolution() {
929        use alloy_json_abi::Function;
930        use alloy_primitives::Address;
931
932        // Create two functions with the same selector but different signatures
933        let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
934        let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
935
936        // Verify they have the same selector (this is the collision)
937        assert_eq!(func1.selector(), func2.selector());
938
939        let functions = vec![func1, func2];
940
941        // Create a mock trace with calldata that matches func1
942        let trace = CallTrace {
943            address: Address::from([0x12; 20]),
944            data: hex!("23b872dd000000000000000000000000000000000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000064").to_vec().into(),
945            ..Default::default()
946        };
947
948        let decoder = CallTraceDecoder::new();
949        let result = decoder.select_contract_function(&functions, &trace);
950
951        // Should return only the function that can decode the calldata (func1)
952        assert_eq!(result.len(), 1);
953        assert_eq!(result[0].signature(), "transferFrom(address,address,uint256)");
954    }
955
956    #[test]
957    fn test_selector_collision_resolution_second_function() {
958        use alloy_json_abi::Function;
959        use alloy_primitives::Address;
960
961        // Create two functions with the same selector but different signatures
962        let func1 = Function::parse("transferFrom(address,address,uint256)").unwrap();
963        let func2 = Function::parse("gasprice_bit_ether(int128)").unwrap();
964
965        let functions = vec![func1, func2];
966
967        // Create a mock trace with calldata that matches func2
968        let trace = CallTrace {
969            address: Address::from([0x12; 20]),
970            data: hex!("23b872dd0000000000000000000000000000000000000000000000000000000000000064")
971                .to_vec()
972                .into(),
973            ..Default::default()
974        };
975
976        let decoder = CallTraceDecoder::new();
977        let result = decoder.select_contract_function(&functions, &trace);
978
979        // Should return only the function that can decode the calldata (func2)
980        assert_eq!(result.len(), 1);
981        assert_eq!(result[0].signature(), "gasprice_bit_ether(int128)");
982    }
983
984    #[test]
985    fn test_should_redact() {
986        let decoder = CallTraceDecoder::new();
987
988        let expected_revert_bytes4 = vec![0xde, 0xad, 0xbe, 0xef];
989        let expect_revert_bytes4_data = Function::parse("expectRevert(bytes4)")
990            .unwrap()
991            .abi_encode_input(&[DynSolValue::FixedBytes(
992                B256::right_padding_from(expected_revert_bytes4.as_slice()),
993                4,
994            )])
995            .unwrap();
996
997        let expected_revert_bytes = hex!(
998            "08c379a000000000000000000000000000000000000000000000000000000000\
999             0000002000000000000000000000000000000000000000000000000000000000\
1000             00000004626f6f6d000000000000000000000000000000000000000000000000"
1001        )
1002        .to_vec();
1003        let expect_revert_bytes_data = Function::parse("expectRevert(bytes)")
1004            .unwrap()
1005            .abi_encode_input(&[DynSolValue::Bytes(expected_revert_bytes.clone())])
1006            .unwrap();
1007
1008        let reverter = Address::from([0x11; 20]);
1009        let expect_revert_bytes4_address_data = Function::parse("expectRevert(bytes4,address)")
1010            .unwrap()
1011            .abi_encode_input(&[
1012                DynSolValue::FixedBytes(
1013                    B256::right_padding_from(expected_revert_bytes4.as_slice()),
1014                    4,
1015                ),
1016                DynSolValue::Address(reverter),
1017            ])
1018            .unwrap();
1019
1020        let count = 42_u64;
1021        let expect_revert_bytes_count_data = Function::parse("expectRevert(bytes,uint64)")
1022            .unwrap()
1023            .abi_encode_input(&[
1024                DynSolValue::Bytes(expected_revert_bytes.clone()),
1025                DynSolValue::Uint(alloy_primitives::U256::from(count), 64),
1026            ])
1027            .unwrap();
1028
1029        let expect_revert_bytes_address_count_data =
1030            Function::parse("expectRevert(bytes,address,uint64)")
1031                .unwrap()
1032                .abi_encode_input(&[
1033                    DynSolValue::Bytes(expected_revert_bytes.clone()),
1034                    DynSolValue::Address(reverter),
1035                    DynSolValue::Uint(alloy_primitives::U256::from(count), 64),
1036                ])
1037                .unwrap();
1038
1039        let expect_revert_runtime_data = expected_revert_bytes4.clone();
1040
1041        // [function_signature, data, expected]
1042        let cheatcode_input_test_cases = vec![
1043            // Should decode the expected revert payload, not full cheatcode calldata:
1044            (
1045                "expectRevert(bytes4)",
1046                expect_revert_bytes4_data,
1047                Some(vec![decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None)]),
1048            ),
1049            (
1050                "expectRevert(bytes)",
1051                expect_revert_bytes_data,
1052                Some(vec![decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None)]),
1053            ),
1054            (
1055                "expectRevert(bytes4)",
1056                expect_revert_runtime_data.clone(),
1057                Some(vec![
1058                    decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None),
1059                ]),
1060            ),
1061            (
1062                "expectRevert(bytes4,address)",
1063                expect_revert_bytes4_address_data,
1064                Some(vec![
1065                    decoder.revert_decoder.decode(expected_revert_bytes4.as_slice(), None),
1066                    decoder.format_value(&DynSolValue::Address(reverter)),
1067                ]),
1068            ),
1069            (
1070                "expectRevert(bytes,uint64)",
1071                expect_revert_bytes_count_data,
1072                Some(vec![
1073                    decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None),
1074                    decoder
1075                        .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)),
1076                ]),
1077            ),
1078            (
1079                "expectRevert(bytes,address,uint64)",
1080                expect_revert_bytes_address_count_data,
1081                Some(vec![
1082                    decoder.revert_decoder.decode(expected_revert_bytes.as_slice(), None),
1083                    decoder.format_value(&DynSolValue::Address(reverter)),
1084                    decoder
1085                        .format_value(&DynSolValue::Uint(alloy_primitives::U256::from(count), 64)),
1086                ]),
1087            ),
1088            (
1089                "expectRevert()",
1090                expect_revert_runtime_data.clone(),
1091                Some(vec![
1092                    decoder.revert_decoder.decode(expect_revert_runtime_data.as_slice(), None),
1093                ]),
1094            ),
1095            // Should redact private key from traces in all cases:
1096            ("addr(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1097            ("createWallet(string)", vec![], Some(vec!["<pk>".to_string()])),
1098            ("createWallet(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1099            ("deriveKey(string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
1100            ("deriveKey(string,string,uint32)", vec![], Some(vec!["<pk>".to_string()])),
1101            ("deriveKey(string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
1102            ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["<pk>".to_string()])),
1103            ("rememberKey(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1104            //
1105            // Should redact private key from traces in specific cases with exceptions:
1106            ("broadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1107            ("broadcast()", vec![], None), // Ignore: `private key` is not passed.
1108            ("startBroadcast(uint256)", vec![], Some(vec!["<pk>".to_string()])),
1109            ("startBroadcast()", vec![], None), // Ignore: `private key` is not passed.
1110            ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["<pk>".to_string()])),
1111            ("getNonce(address)", vec![], None), // Ignore: `address` is public.
1112            //
1113            // Should redact private key and replace in trace in cases:
1114            (
1115                "sign(uint256,bytes32)",
1116                hex!(
1117                    "
1118                    e341eaa4
1119                    7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
1120                    0000000000000000000000000000000000000000000000000000000000000000
1121                "
1122                )
1123                .to_vec(),
1124                Some(vec![
1125                    "\"<pk>\"".to_string(),
1126                    "0x0000000000000000000000000000000000000000000000000000000000000000"
1127                        .to_string(),
1128                ]),
1129            ),
1130            (
1131                "signP256(uint256,bytes32)",
1132                hex!(
1133                    "
1134                    83211b40
1135                    7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
1136                    0000000000000000000000000000000000000000000000000000000000000000
1137                "
1138                )
1139                .to_vec(),
1140                Some(vec![
1141                    "\"<pk>\"".to_string(),
1142                    "0x0000000000000000000000000000000000000000000000000000000000000000"
1143                        .to_string(),
1144                ]),
1145            ),
1146            (
1147                // cast calldata "createFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key"
1148                "createFork(string)",
1149                hex!(
1150                    "
1151                    31ba3498
1152                    0000000000000000000000000000000000000000000000000000000000000020
1153                    000000000000000000000000000000000000000000000000000000000000002c
1154                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1155                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1156                    "
1157                )
1158                .to_vec(),
1159                Some(vec!["\"<rpc url>\"".to_string()]),
1160            ),
1161            (
1162                // cast calldata "createFork(string)" "wss://eth-mainnet.g.alchemy.com/v2/api_key"
1163                "createFork(string)",
1164                hex!(
1165                    "
1166                    31ba3498
1167                    0000000000000000000000000000000000000000000000000000000000000020
1168                    000000000000000000000000000000000000000000000000000000000000002a
1169                    7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f
1170                    76322f6170695f6b657900000000000000000000000000000000000000000000
1171                    "
1172                )
1173                .to_vec(),
1174                Some(vec!["\"<rpc url>\"".to_string()]),
1175            ),
1176            (
1177                // cast calldata "createFork(string)" "mainnet"
1178                "createFork(string)",
1179                hex!(
1180                    "
1181                    31ba3498
1182                    0000000000000000000000000000000000000000000000000000000000000020
1183                    0000000000000000000000000000000000000000000000000000000000000007
1184                    6d61696e6e657400000000000000000000000000000000000000000000000000
1185                    "
1186                )
1187                .to_vec(),
1188                Some(vec!["\"mainnet\"".to_string()]),
1189            ),
1190            (
1191                // cast calldata "createFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1
1192                "createFork(string,uint256)",
1193                hex!(
1194                    "
1195                    6ba3ba2b
1196                    0000000000000000000000000000000000000000000000000000000000000040
1197                    0000000000000000000000000000000000000000000000000000000000000001
1198                    000000000000000000000000000000000000000000000000000000000000002c
1199                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1200                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1201                "
1202                )
1203                .to_vec(),
1204                Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1205            ),
1206            (
1207                // cast calldata "createFork(string,uint256)" "mainnet" 1
1208                "createFork(string,uint256)",
1209                hex!(
1210                    "
1211                    6ba3ba2b
1212                    0000000000000000000000000000000000000000000000000000000000000040
1213                    0000000000000000000000000000000000000000000000000000000000000001
1214                    0000000000000000000000000000000000000000000000000000000000000007
1215                    6d61696e6e657400000000000000000000000000000000000000000000000000
1216                "
1217                )
1218                .to_vec(),
1219                Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1220            ),
1221            (
1222                // cast calldata "createFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1223                "createFork(string,bytes32)",
1224                hex!(
1225                    "
1226                    7ca29682
1227                    0000000000000000000000000000000000000000000000000000000000000040
1228                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1229                    000000000000000000000000000000000000000000000000000000000000002c
1230                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1231                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1232                "
1233                )
1234                .to_vec(),
1235                Some(vec![
1236                    "\"<rpc url>\"".to_string(),
1237                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1238                        .to_string(),
1239                ]),
1240            ),
1241            (
1242                // cast calldata "createFork(string,bytes32)" "mainnet"
1243                // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1244                "createFork(string,bytes32)",
1245                hex!(
1246                    "
1247                    7ca29682
1248                    0000000000000000000000000000000000000000000000000000000000000040
1249                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1250                    0000000000000000000000000000000000000000000000000000000000000007
1251                    6d61696e6e657400000000000000000000000000000000000000000000000000
1252                "
1253                )
1254                .to_vec(),
1255                Some(vec![
1256                    "\"mainnet\"".to_string(),
1257                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1258                        .to_string(),
1259                ]),
1260            ),
1261            (
1262                // cast calldata "createSelectFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key"
1263                "createSelectFork(string)",
1264                hex!(
1265                    "
1266                    98680034
1267                    0000000000000000000000000000000000000000000000000000000000000020
1268                    000000000000000000000000000000000000000000000000000000000000002c
1269                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1270                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1271                    "
1272                )
1273                .to_vec(),
1274                Some(vec!["\"<rpc url>\"".to_string()]),
1275            ),
1276            (
1277                // cast calldata "createSelectFork(string)" "mainnet"
1278                "createSelectFork(string)",
1279                hex!(
1280                    "
1281                    98680034
1282                    0000000000000000000000000000000000000000000000000000000000000020
1283                    0000000000000000000000000000000000000000000000000000000000000007
1284                    6d61696e6e657400000000000000000000000000000000000000000000000000
1285                    "
1286                )
1287                .to_vec(),
1288                Some(vec!["\"mainnet\"".to_string()]),
1289            ),
1290            (
1291                // cast calldata "createSelectFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1
1292                "createSelectFork(string,uint256)",
1293                hex!(
1294                    "
1295                    71ee464d
1296                    0000000000000000000000000000000000000000000000000000000000000040
1297                    0000000000000000000000000000000000000000000000000000000000000001
1298                    000000000000000000000000000000000000000000000000000000000000002c
1299                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1300                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1301                "
1302                )
1303                .to_vec(),
1304                Some(vec!["\"<rpc url>\"".to_string(), "1".to_string()]),
1305            ),
1306            (
1307                // cast calldata "createSelectFork(string,uint256)" "mainnet" 1
1308                "createSelectFork(string,uint256)",
1309                hex!(
1310                    "
1311                    71ee464d
1312                    0000000000000000000000000000000000000000000000000000000000000040
1313                    0000000000000000000000000000000000000000000000000000000000000001
1314                    0000000000000000000000000000000000000000000000000000000000000007
1315                    6d61696e6e657400000000000000000000000000000000000000000000000000
1316                "
1317                )
1318                .to_vec(),
1319                Some(vec!["\"mainnet\"".to_string(), "1".to_string()]),
1320            ),
1321            (
1322                // cast calldata "createSelectFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1323                "createSelectFork(string,bytes32)",
1324                hex!(
1325                    "
1326                    84d52b7a
1327                    0000000000000000000000000000000000000000000000000000000000000040
1328                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1329                    000000000000000000000000000000000000000000000000000000000000002c
1330                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1331                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1332                "
1333                )
1334                .to_vec(),
1335                Some(vec![
1336                    "\"<rpc url>\"".to_string(),
1337                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1338                        .to_string(),
1339                ]),
1340            ),
1341            (
1342                // cast calldata "createSelectFork(string,bytes32)" "mainnet"
1343                // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1344                "createSelectFork(string,bytes32)",
1345                hex!(
1346                    "
1347                    84d52b7a
1348                    0000000000000000000000000000000000000000000000000000000000000040
1349                    ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
1350                    0000000000000000000000000000000000000000000000000000000000000007
1351                    6d61696e6e657400000000000000000000000000000000000000000000000000
1352                "
1353                )
1354                .to_vec(),
1355                Some(vec![
1356                    "\"mainnet\"".to_string(),
1357                    "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
1358                        .to_string(),
1359                ]),
1360            ),
1361            (
1362                // cast calldata "rpc(string,string,string)" "https://eth-mainnet.g.alchemy.com/v2/api_key" "eth_getBalance" "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"
1363                "rpc(string,string,string)",
1364                hex!(
1365                    "
1366                    0199a220
1367                    0000000000000000000000000000000000000000000000000000000000000060
1368                    00000000000000000000000000000000000000000000000000000000000000c0
1369                    0000000000000000000000000000000000000000000000000000000000000100
1370                    000000000000000000000000000000000000000000000000000000000000002c
1371                    68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f
1372                    6d2f76322f6170695f6b65790000000000000000000000000000000000000000
1373                    000000000000000000000000000000000000000000000000000000000000000e
1374                    6574685f67657442616c616e6365000000000000000000000000000000000000
1375                    0000000000000000000000000000000000000000000000000000000000000034
1376                    5b22307835353165373738343737386566386530343865343935646634396632
1377                    363134663834613466316463222c22307830225d000000000000000000000000
1378                "
1379                )
1380                .to_vec(),
1381                Some(vec![
1382                    "\"<rpc url>\"".to_string(),
1383                    "\"eth_getBalance\"".to_string(),
1384                    "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1385                        .to_string(),
1386                ]),
1387            ),
1388            (
1389                // cast calldata "rpc(string,string,string)" "mainnet" "eth_getBalance"
1390                // "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]"
1391                "rpc(string,string,string)",
1392                hex!(
1393                    "
1394                    0199a220
1395                    0000000000000000000000000000000000000000000000000000000000000060
1396                    00000000000000000000000000000000000000000000000000000000000000a0
1397                    00000000000000000000000000000000000000000000000000000000000000e0
1398                    0000000000000000000000000000000000000000000000000000000000000007
1399                    6d61696e6e657400000000000000000000000000000000000000000000000000
1400                    000000000000000000000000000000000000000000000000000000000000000e
1401                    6574685f67657442616c616e6365000000000000000000000000000000000000
1402                    0000000000000000000000000000000000000000000000000000000000000034
1403                    5b22307835353165373738343737386566386530343865343935646634396632
1404                    363134663834613466316463222c22307830225d000000000000000000000000
1405                "
1406                )
1407                .to_vec(),
1408                Some(vec![
1409                    "\"mainnet\"".to_string(),
1410                    "\"eth_getBalance\"".to_string(),
1411                    "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\""
1412                        .to_string(),
1413                ]),
1414            ),
1415        ];
1416
1417        // [function_signature, expected]
1418        let cheatcode_output_test_cases = vec![
1419            // Should redact private key on output in all cases:
1420            ("createWallet(string)", Some("<pk>".to_string())),
1421            ("deriveKey(string,uint32)", Some("<pk>".to_string())),
1422            // Should redact RPC URL if defined, except if referenced by an alias:
1423            ("rpcUrl(string)", Some("<rpc url>".to_string())),
1424            ("rpcUrls()", Some("<rpc url>".to_string())),
1425            ("rpcUrlStructs()", Some("<rpc url>".to_string())),
1426        ];
1427
1428        for (function_signature, data, expected) in cheatcode_input_test_cases {
1429            let function = Function::parse(function_signature).unwrap();
1430            let result = decoder.decode_cheatcode_inputs(&function, &data);
1431            assert_eq!(result, expected, "Input case failed for: {function_signature}");
1432        }
1433
1434        for (function_signature, expected) in cheatcode_output_test_cases {
1435            let function = Function::parse(function_signature).unwrap();
1436            let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default());
1437            assert_eq!(result, expected, "Output case failed for: {function_signature}");
1438        }
1439    }
1440
1441    // A mock identifier that records which addresses it was asked to identify.
1442    struct RecordingIdentifier {
1443        queried: Vec<Address>,
1444    }
1445    impl TraceIdentifier for RecordingIdentifier {
1446        fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec<IdentifiedAddress<'_>> {
1447            self.queried.extend(nodes.iter().map(|n| n.trace.address));
1448            Vec::new()
1449        }
1450    }
1451
1452    #[test]
1453    fn test_identify_addresses_skips_evm_precompiles() {
1454        use foundry_evm_core::precompiles::SHA_256;
1455
1456        let decoder = CallTraceDecoder::new();
1457
1458        let mut arena = CallTraceArena::default();
1459        let regular_addr = Address::from([0x42; 20]);
1460        arena.nodes_mut()[0].trace.address = regular_addr;
1461
1462        // Standard EVM precompile flagged by the inspector.
1463        arena.nodes_mut().push(CallTraceNode {
1464            trace: CallTrace {
1465                address: SHA_256,
1466                depth: 1,
1467                maybe_precompile: Some(true),
1468                ..Default::default()
1469            },
1470            idx: 1,
1471            ..Default::default()
1472        });
1473
1474        // Standard EVM precompile NOT flagged, caught by is_known_precompile.
1475        arena.nodes_mut().push(CallTraceNode {
1476            trace: CallTrace {
1477                address: SHA_256,
1478                depth: 1,
1479                maybe_precompile: None,
1480                ..Default::default()
1481            },
1482            idx: 2,
1483            ..Default::default()
1484        });
1485
1486        let mut identifier = RecordingIdentifier { queried: Vec::new() };
1487        decoder.identify_addresses(&arena, &mut identifier);
1488
1489        assert_eq!(identifier.queried, vec![regular_addr]);
1490    }
1491
1492    #[test]
1493    fn test_identify_addresses_skips_tempo_precompiles() {
1494        use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES;
1495
1496        // Decoder with Tempo chain ID (4217).
1497        let mut decoder = CallTraceDecoder::new().clone();
1498        decoder.chain_id = Some(4217);
1499
1500        let mut arena = CallTraceArena::default();
1501        let regular_addr = Address::from([0x42; 20]);
1502        arena.nodes_mut()[0].trace.address = regular_addr;
1503
1504        // Tempo precompile — not flagged by inspector, caught by is_known_precompile
1505        // only when chain_id is a Tempo chain.
1506        let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0];
1507        arena.nodes_mut().push(CallTraceNode {
1508            trace: CallTrace {
1509                address: tempo_precompile,
1510                depth: 1,
1511                maybe_precompile: None,
1512                ..Default::default()
1513            },
1514            idx: 1,
1515            ..Default::default()
1516        });
1517
1518        let mut identifier = RecordingIdentifier { queried: Vec::new() };
1519        decoder.identify_addresses(&arena, &mut identifier);
1520
1521        // On a Tempo chain, the Tempo precompile should be filtered out.
1522        assert_eq!(identifier.queried, vec![regular_addr]);
1523    }
1524
1525    #[test]
1526    fn test_identify_addresses_does_not_skip_tempo_precompiles_on_other_chains() {
1527        use foundry_evm_core::tempo::TEMPO_PRECOMPILE_ADDRESSES;
1528
1529        // Decoder with Ethereum mainnet chain ID (1).
1530        let mut decoder = CallTraceDecoder::new().clone();
1531        decoder.chain_id = Some(1);
1532
1533        let mut arena = CallTraceArena::default();
1534        let regular_addr = Address::from([0x42; 20]);
1535        arena.nodes_mut()[0].trace.address = regular_addr;
1536
1537        let tempo_precompile = TEMPO_PRECOMPILE_ADDRESSES[0];
1538        arena.nodes_mut().push(CallTraceNode {
1539            trace: CallTrace {
1540                address: tempo_precompile,
1541                depth: 1,
1542                maybe_precompile: None,
1543                ..Default::default()
1544            },
1545            idx: 1,
1546            ..Default::default()
1547        });
1548
1549        let mut identifier = RecordingIdentifier { queried: Vec::new() };
1550        decoder.identify_addresses(&arena, &mut identifier);
1551
1552        // On Ethereum, Tempo precompile addresses are regular contracts — should NOT be filtered.
1553        assert_eq!(identifier.queried, vec![regular_addr, tempo_precompile]);
1554    }
1555}