1use alloy::dyn_abi::{DynSolType, DynSolValue};
15
16pub fn parse_custom_error(output: &[u8]) -> Option<String> {
38 if output.len() < 4 {
39 return None;
40 }
41
42 let selector = &output[0..4];
43 match selector {
44 [0x08, 0xc3, 0x79, 0xa0] => {
46 if let Ok(DynSolValue::String(reason)) = DynSolType::String.abi_decode(&output[4..]) {
47 Some(reason)
48 } else {
49 None
50 }
51 }
52 [0x4e, 0x48, 0x7b, 0x71] => {
54 if let Ok(DynSolValue::Uint(code, _)) = DynSolType::Uint(256).abi_decode(&output[4..]) {
55 return Some(match code.to::<u64>() {
56 0x01 => "Panic: Assertion failed".to_string(),
57 0x11 => "Panic: Arithmetic overflow".to_string(),
58 0x12 => "Panic: Division by zero".to_string(),
59 0x21 => "Panic: Invalid array access".to_string(),
60 0x22 => "Panic: Array access out of bounds".to_string(),
61 0x31 => "Panic: Invalid enum value".to_string(),
62 0x32 => "Panic: Invalid storage access".to_string(),
63 0x41 => "Panic: Zero initialization".to_string(),
64 0x51 => "Panic: Invalid calldata access".to_string(),
65 code => format!("Panic: Unknown error code (0x{code:x})"),
66 });
67 }
68 None
69 }
70 _ => None,
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use alloy::primitives::hex::decode;
78
79 #[test]
80 fn test_parse_error_string() {
81 let error_bytes = decode(
83 "08c379a0\
84 0000000000000000000000000000000000000000000000000000000000000020\
85 0000000000000000000000000000000000000000000000000000000000000014\
86 496e73756666696369656e742062616c616e636500000000000000000000000000",
87 )
88 .unwrap();
89
90 let result = parse_custom_error(&error_bytes);
91 assert_eq!(result, Some("Insufficient balance".to_string()));
92
93 let invalid_bytes = decode("08c379a0").unwrap();
95 assert_eq!(parse_custom_error(&invalid_bytes), None);
96 }
97
98 #[test]
99 fn test_parse_panic() {
100 let panic_codes = [
102 (0x01, "Panic: Assertion failed"),
103 (0x11, "Panic: Arithmetic overflow"),
104 (0x12, "Panic: Division by zero"),
105 (0x21, "Panic: Invalid array access"),
106 (0x22, "Panic: Array access out of bounds"),
107 (0x31, "Panic: Invalid enum value"),
108 (0x32, "Panic: Invalid storage access"),
109 (0x41, "Panic: Zero initialization"),
110 (0x51, "Panic: Invalid calldata access"),
111 (0xFF, "Panic: Unknown error code (0xff)"),
112 ];
113
114 for (code, expected_message) in panic_codes {
115 let panic_bytes = [
117 0x4e, 0x48, 0x7b, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
120 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
121 0x00, 0x00, 0x00, code as u8,
122 ];
123
124 let result = parse_custom_error(&panic_bytes);
125 assert_eq!(result, Some(expected_message.to_string()));
126 }
127 }
128
129 #[test]
130 fn test_invalid_inputs() {
131 assert_eq!(parse_custom_error(&[]), None);
133
134 assert_eq!(parse_custom_error(&[0x08, 0xc3, 0x79]), None);
136
137 assert_eq!(parse_custom_error(&[0x00, 0x00, 0x00, 0x00]), None);
139
140 let invalid_panic = [
142 0x4e, 0x48, 0x7b, 0x71, 0x00, ];
145 assert_eq!(parse_custom_error(&invalid_panic), None);
146 }
147}