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