From eb3ed12e5e8bdbcfd8db920ca87ea4cf98a5f584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tino=20Mart=C3=ADnez=20Molina?= Date: Wed, 25 Mar 2026 23:12:46 -0700 Subject: [PATCH] refactor: decouple revert decoding from selector resolution --- src/utils/abi_decoder.rs | 72 +++++-------------------------------- src/utils/trace_renderer.rs | 31 +++++++++++----- 2 files changed, 30 insertions(+), 73 deletions(-) diff --git a/src/utils/abi_decoder.rs b/src/utils/abi_decoder.rs index 4528619..f2b4206 100644 --- a/src/utils/abi_decoder.rs +++ b/src/utils/abi_decoder.rs @@ -1,5 +1,5 @@ use super::hex_utils; -use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt}; +use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::Function; /// Decode ABI-encoded function arguments (strips 4-byte selector). @@ -30,7 +30,7 @@ pub fn can_decode(signature: &str, calldata: &str) -> bool { /// Decode a revert reason from raw output bytes (0x-prefixed hex). /// /// Recognises `Error(string)` (0x08c379a0) and `Panic(uint256)` (0x4e487b71). -/// Returns `None` for unknown selectors — use [`decode_custom_revert`] as fallback. +/// Returns `None` for unknown selectors. pub fn decode_revert_reason(output: &str) -> Option { let hex = hex_utils::strip_0x(output); if hex.len() < 8 { @@ -60,35 +60,6 @@ pub fn decode_revert_reason(output: &str) -> Option { } } -/// Try to decode a custom error by resolving its 4-byte selector via Sourcify. -/// -/// Falls back to returning the raw signature if arguments cannot be decoded. -pub fn decode_custom_revert( - output: &str, - resolver: &mut super::selector_resolver::SelectorResolver, -) -> Option { - let hex = hex_utils::strip_0x(output); - if hex.len() < 8 { - return None; - } - - let selector = &hex[..8]; - if selector == "08c379a0" || selector == "4e487b71" { - return None; - } - - let prefixed = format!("0x{selector}"); - let signature = resolver.resolve(&prefixed, Some(output))?; - let name = signature.split('(').next().unwrap_or(&signature); - - if let Some(args) = decode_function_args(&signature, output) { - let formatted: Vec = args.iter().map(|(_, v)| format_value(v)).collect(); - Some(format!("{name}({})", formatted.join(", "))) - } else { - Some(signature) - } -} - pub fn format_value(value: &DynSolValue) -> String { match value { DynSolValue::Address(a) => format!("{a:#x}"), @@ -115,19 +86,17 @@ fn decode_hex(input: &str) -> Option> { } fn decode_with_function(signature: &str, data: &[u8]) -> Option> { - let func = Function::parse(signature).ok()?; - let types = resolve_types(&func)?; + let func: Function = Function::parse(signature).ok()?; let values = func.abi_decode_input(data).ok()?; - Some(types.into_iter().zip(values).collect()) -} - -fn resolve_types(func: &Function) -> Option> { func.inputs .iter() - .map(|p| p.selector_type().parse().ok()) - .collect() + .zip(values) + .map(|(p, v)| p.resolve().ok().map(|t| (t, v))) + .collect::>>() } +/// Get the description of a Solidity panic code. +/// fn panic_description(code: &str) -> &'static str { match code { "0" => "generic/compiler-inserted", @@ -260,29 +229,4 @@ mod tests { fn test_decode_revert_too_short() { assert!(decode_revert_reason("0x1234").is_none()); } - - #[test] - fn test_decode_custom_revert_skips_builtins() { - let mut resolver = crate::utils::selector_resolver::SelectorResolver::new( - reqwest::blocking::Client::new(), - false, - None, - ); - let output = "0x08c379a0\ - 0000000000000000000000000000000000000000000000000000000000000020\ - 0000000000000000000000000000000000000000000000000000000000000005\ - 68656c6c6f000000000000000000000000000000000000000000000000000000"; - assert!(decode_custom_revert(output, &mut resolver).is_none()); - } - - #[test] - fn test_decode_custom_revert_unknown_no_resolver() { - let mut resolver = crate::utils::selector_resolver::SelectorResolver::new( - reqwest::blocking::Client::new(), - false, - None, - ); - let output = "0xdeadbeef0000000000000000000000000000000000000000000000000000000000000001"; - assert!(decode_custom_revert(output, &mut resolver).is_none()); - } } diff --git a/src/utils/trace_renderer.rs b/src/utils/trace_renderer.rs index 3d1f916..32a8dd9 100644 --- a/src/utils/trace_renderer.rs +++ b/src/utils/trace_renderer.rs @@ -160,15 +160,7 @@ fn print_call( println!("{prefix}{connector}{gas_display} {call_desc}{call_type_suffix}"); if let Some(err) = &node.error { - let reason = node - .output - .as_deref() - .and_then(abi_decoder::decode_revert_reason) - .or_else(|| { - node.output - .as_deref() - .and_then(|o| abi_decoder::decode_custom_revert(o, resolver)) - }); + let reason = resolve_revert_reason(node.output.as_deref(), resolver); let err_msg = if let Some(reason) = reason { pal.red(&format!("↳ error: {err} — {reason}")) } else { @@ -331,6 +323,27 @@ fn print_arg( } } +fn resolve_revert_reason(output: Option<&str>, resolver: &mut SelectorResolver) -> Option { + if let Some(reason) = output.and_then(abi_decoder::decode_revert_reason) { + return Some(reason); + } + + let output = output?; + let selector = output.get(..10)?; + let sig = resolver.resolve(selector, Some(output))?; + let name = sig.split('(').next().unwrap_or(&sig); + + if let Some(args) = abi_decoder::decode_function_args(&sig, output) { + let fmt: Vec<_> = args + .iter() + .map(|(_, v)| abi_decoder::format_value(v)) + .collect(); + Some(format!("{name}({})", fmt.join(", "))) + } else { + Some(sig) + } +} + #[cfg(test)] mod tests { use super::*;