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