foundry_evm_traces/decoder/
mod.rs

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