use alloy::dyn_abi::{DynSolType, DynSolValue};
pub fn parse_custom_error(output: &[u8]) -> Option<String> {
if output.len() < 4 {
return None;
}
let selector = &output[0..4];
match selector {
[0x08, 0xc3, 0x79, 0xa0] => {
if let Ok(DynSolValue::String(reason)) = DynSolType::String.abi_decode(&output[4..]) {
Some(reason)
} else {
None
}
}
[0x4e, 0x48, 0x7b, 0x71] => {
if let Ok(DynSolValue::Uint(code, _)) = DynSolType::Uint(256).abi_decode(&output[4..]) {
return Some(match code.to::<u64>() {
0x01 => "Panic: Assertion failed".to_string(),
0x11 => "Panic: Arithmetic overflow".to_string(),
0x12 => "Panic: Division by zero".to_string(),
0x21 => "Panic: Invalid array access".to_string(),
0x22 => "Panic: Array access out of bounds".to_string(),
0x31 => "Panic: Invalid enum value".to_string(),
0x32 => "Panic: Invalid storage access".to_string(),
0x41 => "Panic: Zero initialization".to_string(),
0x51 => "Panic: Invalid calldata access".to_string(),
code => format!("Panic: Unknown error code (0x{code:x})"),
});
}
None
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::hex::decode;
#[test]
fn test_parse_error_string() {
let error_bytes = decode(
"08c379a0\
0000000000000000000000000000000000000000000000000000000000000020\
0000000000000000000000000000000000000000000000000000000000000014\
496e73756666696369656e742062616c616e636500000000000000000000000000",
)
.unwrap();
let result = parse_custom_error(&error_bytes);
assert_eq!(result, Some("Insufficient balance".to_string()));
let invalid_bytes = decode("08c379a0").unwrap();
assert_eq!(parse_custom_error(&invalid_bytes), None);
}
#[test]
fn test_parse_panic() {
let panic_codes = [
(0x01, "Panic: Assertion failed"),
(0x11, "Panic: Arithmetic overflow"),
(0x12, "Panic: Division by zero"),
(0x21, "Panic: Invalid array access"),
(0x22, "Panic: Array access out of bounds"),
(0x31, "Panic: Invalid enum value"),
(0x32, "Panic: Invalid storage access"),
(0x41, "Panic: Zero initialization"),
(0x51, "Panic: Invalid calldata access"),
(0xFF, "Panic: Unknown error code (0xff)"),
];
for (code, expected_message) in panic_codes {
let panic_bytes = [
0x4e, 0x48, 0x7b, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, code as u8,
];
let result = parse_custom_error(&panic_bytes);
assert_eq!(result, Some(expected_message.to_string()));
}
}
#[test]
fn test_invalid_inputs() {
assert_eq!(parse_custom_error(&[]), None);
assert_eq!(parse_custom_error(&[0x08, 0xc3, 0x79]), None);
assert_eq!(parse_custom_error(&[0x00, 0x00, 0x00, 0x00]), None);
let invalid_panic = [
0x4e, 0x48, 0x7b, 0x71, 0x00, ];
assert_eq!(parse_custom_error(&invalid_panic), None);
}
}