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