Skip to main content

foundry_evm_traces/decoder/
mod.rs

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