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