1use std::sync::Arc;
11
12use chainerrors_core::decoder::{DecodeErrorError, ErrorDecoder};
13use chainerrors_core::registry::{ErrorSignature, ErrorSignatureRegistry, MemoryErrorRegistry};
14use chainerrors_core::types::{DecodedError, ErrorContext, ErrorKind};
15
16use crate::custom::decode_custom_error;
17use crate::panic::{decode_panic, PANIC_SELECTOR};
18use crate::revert::{decode_error_string, ERROR_STRING_SELECTOR};
19
20pub struct EvmErrorDecoder {
32 registry: Arc<dyn ErrorSignatureRegistry>,
33}
34
35impl EvmErrorDecoder {
36 pub fn new() -> Self {
38 let reg = Arc::new(MemoryErrorRegistry::new());
39 Self::with_bundled_signatures(®);
40 Self { registry: reg }
41 }
42
43 pub fn with_registry(registry: Arc<dyn ErrorSignatureRegistry>) -> Self {
45 Self { registry }
46 }
47
48 fn with_bundled_signatures(reg: &MemoryErrorRegistry) {
50 use chainerrors_core::registry::{ErrorParam, ErrorSignature};
51 use tiny_keccak::{Hasher, Keccak};
52
53 fn sel(sig: &str) -> [u8; 4] {
54 let mut k = Keccak::v256();
55 k.update(sig.as_bytes());
56 let mut out = [0u8; 32];
57 k.finalize(&mut out);
58 [out[0], out[1], out[2], out[3]]
59 }
60
61 fn p(name: &str, ty: &str) -> ErrorParam {
62 ErrorParam { name: name.to_string(), ty: ty.to_string() }
63 }
64
65 let bundled: &[(&str, &str, &[(&str, &str)], Option<&str>)] = &[
66 ("ERC20InsufficientBalance", "ERC20InsufficientBalance(address,uint256,uint256)",
68 &[("sender","address"),("balance","uint256"),("needed","uint256")],
69 Some("The sender does not have enough token balance for this transfer.")),
70 ("ERC20InvalidSender", "ERC20InvalidSender(address)",
71 &[("sender","address")], None),
72 ("ERC20InvalidReceiver", "ERC20InvalidReceiver(address)",
73 &[("receiver","address")], None),
74 ("ERC20InsufficientAllowance", "ERC20InsufficientAllowance(address,uint256,uint256)",
75 &[("spender","address"),("allowance","uint256"),("needed","uint256")],
76 Some("Increase the token allowance before calling transferFrom.")),
77 ("ERC20InvalidApprover", "ERC20InvalidApprover(address)",
78 &[("approver","address")], None),
79 ("ERC20InvalidSpender", "ERC20InvalidSpender(address)",
80 &[("spender","address")], None),
81
82 ("ERC721InvalidOwner", "ERC721InvalidOwner(address)",
84 &[("owner","address")], None),
85 ("ERC721NonexistentToken", "ERC721NonexistentToken(uint256)",
86 &[("tokenId","uint256")], None),
87 ("ERC721IncorrectOwner", "ERC721IncorrectOwner(address,uint256,address)",
88 &[("sender","address"),("tokenId","uint256"),("owner","address")], None),
89 ("ERC721InvalidSender", "ERC721InvalidSender(address)",
90 &[("sender","address")], None),
91 ("ERC721InvalidReceiver", "ERC721InvalidReceiver(address)",
92 &[("receiver","address")], None),
93 ("ERC721InsufficientApproval", "ERC721InsufficientApproval(address,uint256)",
94 &[("operator","address"),("tokenId","uint256")], None),
95 ("ERC721InvalidApprover", "ERC721InvalidApprover(address)",
96 &[("approver","address")], None),
97 ("ERC721InvalidOperator", "ERC721InvalidOperator(address)",
98 &[("operator","address")], None),
99
100 ("OwnableUnauthorizedAccount", "OwnableUnauthorizedAccount(address)",
102 &[("account","address")],
103 Some("Only the owner can call this function. Ensure you are using the owner address.")),
104 ("OwnableInvalidOwner", "OwnableInvalidOwner(address)",
105 &[("owner","address")], None),
106
107 ("AccessControlUnauthorizedAccount", "AccessControlUnauthorizedAccount(address,bytes32)",
109 &[("account","address"),("neededRole","bytes32")],
110 Some("The caller is missing the required role. Grant the role with grantRole().")),
111 ("AccessControlBadConfirmation", "AccessControlBadConfirmation()",
112 &[], None),
113
114 ("ReentrancyGuardReentrantCall", "ReentrancyGuardReentrantCall()",
116 &[], Some("Reentrancy detected. Do not call this function recursively.")),
117
118 ("EnforcedPause", "EnforcedPause()",
120 &[], Some("The contract is paused. Wait for it to be unpaused.")),
121 ("ExpectedPause", "ExpectedPause()",
122 &[], None),
123
124 ("T", "T()", &[], Some("Uniswap V3: tick out of range.")),
126 ("LOK", "LOK()", &[], Some("Uniswap V3: pool is locked.")),
127 ("TLU", "TLU()", &[], Some("Uniswap V3: tick lower >= tick upper.")),
128 ("TLM", "TLM()", &[], Some("Uniswap V3: tick lower too low.")),
129 ("TUM", "TUM()", &[], Some("Uniswap V3: tick upper too high.")),
130 ("AS", "AS()", &[], Some("Uniswap V3: amount specified is zero.")),
131 ("M0", "M0()", &[], Some("Uniswap V3: mint amounts are zero.")),
132 ("M1", "M1()", &[], Some("Uniswap V3: mint amount0 exceeds limit.")),
133 ("IIA", "IIA()", &[], Some("Uniswap V3: insufficient input amount.")),
134 ("SPL", "SPL()", &[], Some("Uniswap V3: sqrt price limit is out of range.")),
135 ("F0", "F0()", &[], Some("Uniswap V3: flash amount0 > balance.")),
136 ("F1", "F1()", &[], Some("Uniswap V3: flash amount1 > balance.")),
137 ("L", "L()", &[], Some("Uniswap V3: liquidity is zero.")),
138 ("LS", "LS()", &[], Some("Uniswap V3: liquidity exceeds maximum.")),
139 ("LA", "LA()", &[], Some("Uniswap V3: liquidity amount overflows.")),
140
141 ("ERC4626ExceededMaxDeposit", "ERC4626ExceededMaxDeposit(address,uint256,uint256)",
147 &[("receiver","address"),("assets","uint256"),("max","uint256")], None),
148 ("ERC4626ExceededMaxMint", "ERC4626ExceededMaxMint(address,uint256,uint256)",
149 &[("receiver","address"),("shares","uint256"),("max","uint256")], None),
150 ("ERC4626ExceededMaxWithdraw", "ERC4626ExceededMaxWithdraw(address,uint256,uint256)",
151 &[("owner","address"),("assets","uint256"),("max","uint256")], None),
152 ("ERC4626ExceededMaxRedeem", "ERC4626ExceededMaxRedeem(address,uint256,uint256)",
153 &[("owner","address"),("shares","uint256"),("max","uint256")], None),
154
155 ("AddressInsufficientBalance", "AddressInsufficientBalance(address)",
157 &[("account","address")], None),
158 ("AddressEmptyCode", "AddressEmptyCode(address)",
159 &[("target","address")],
160 Some("The target address has no contract code deployed.")),
161 ("FailedInnerCall", "FailedInnerCall()",
162 &[], None),
163
164 ("SafeERC20FailedOperation", "SafeERC20FailedOperation(address)",
166 &[("token","address")],
167 Some("The ERC-20 token operation failed. Ensure the token is compliant.")),
168 ("SafeERC20FailedDecreaseAllowance", "SafeERC20FailedDecreaseAllowance(address,uint256)",
169 &[("spender","address"),("currentAllowance","uint256")], None),
170 ];
171
172 for (name, sig_str, raw_inputs, hint) in bundled {
173 let inputs = raw_inputs
174 .iter()
175 .map(|(n, t)| p(n, t))
176 .collect::<Vec<_>>();
177 reg.register(ErrorSignature {
178 name: name.to_string(),
179 signature: sig_str.to_string(),
180 selector: sel(sig_str),
181 inputs,
182 source: "bundled".to_string(),
183 suggestion: hint.map(|s| s.to_string()),
184 });
185 }
186 }
187
188 pub fn register_signature(&self, _sig: ErrorSignature) {
190 }
193}
194
195impl Default for EvmErrorDecoder {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201impl ErrorDecoder for EvmErrorDecoder {
202 fn chain_family(&self) -> &'static str {
203 "evm"
204 }
205
206 fn decode(
207 &self,
208 revert_data: &[u8],
209 ctx: Option<ErrorContext>,
210 ) -> Result<DecodedError, DecodeErrorError> {
211 if revert_data.is_empty() {
213 return Ok(DecodedError {
214 kind: ErrorKind::Empty,
215 raw_data: vec![],
216 selector: None,
217 suggestion: Some("Transaction reverted with no error message.".into()),
218 confidence: 0.5,
219 context: ctx,
220 });
221 }
222
223 let selector: Option<[u8; 4]> = if revert_data.len() >= 4 {
225 Some(revert_data[..4].try_into().unwrap())
226 } else {
227 None
228 };
229
230 if let Some(message) = decode_error_string(revert_data) {
232 let suggestion = generate_revert_suggestion(&message);
233 return Ok(DecodedError {
234 kind: ErrorKind::RevertString { message },
235 raw_data: revert_data.to_vec(),
236 selector: Some(ERROR_STRING_SELECTOR),
237 suggestion,
238 confidence: 1.0,
239 context: ctx,
240 });
241 }
242
243 if let Some((code, meaning)) = decode_panic(revert_data) {
245 return Ok(DecodedError {
246 kind: ErrorKind::Panic {
247 code,
248 meaning: meaning.to_string(),
249 },
250 raw_data: revert_data.to_vec(),
251 selector: Some(PANIC_SELECTOR),
252 suggestion: Some(format!(
253 "Solidity assert violation (panic code 0x{code:02x}): {meaning}."
254 )),
255 confidence: 1.0,
256 context: ctx,
257 });
258 }
259
260 if let Some(kind) = decode_custom_error(revert_data, self.registry.as_ref()) {
262 let suggestion = if let ErrorKind::CustomError { ref name, .. } = kind {
263 self.registry
265 .get_by_name(name)
266 .and_then(|s| s.suggestion)
267 } else {
268 None
269 };
270 return Ok(DecodedError {
271 kind,
272 raw_data: revert_data.to_vec(),
273 selector,
274 suggestion,
275 confidence: 0.95,
276 context: ctx,
277 });
278 }
279
280 let selector_hex = selector
282 .map(|s| hex::encode(s))
283 .unwrap_or_else(|| "none".into());
284
285 Ok(DecodedError {
286 kind: ErrorKind::RawRevert {
287 selector: selector_hex,
288 data: revert_data.to_vec(),
289 },
290 raw_data: revert_data.to_vec(),
291 selector,
292 suggestion: Some(
293 "Unknown error selector. Try looking up the selector on https://4byte.directory"
294 .into(),
295 ),
296 confidence: 0.0,
297 context: ctx,
298 })
299 }
300}
301
302fn generate_revert_suggestion(message: &str) -> Option<String> {
304 let msg_lower = message.to_lowercase();
305 if msg_lower.contains("not the owner") || msg_lower.contains("not owner") {
306 Some("Ensure the caller is the contract owner.".into())
307 } else if msg_lower.contains("insufficient") && msg_lower.contains("balance") {
308 Some("The account balance is too low. Check the token balance before calling.".into())
309 } else if msg_lower.contains("allowance") {
310 Some("Increase the token allowance with approve() before calling transferFrom().".into())
311 } else if msg_lower.contains("paused") {
312 Some("The contract is paused. Wait for it to be unpaused.".into())
313 } else if msg_lower.contains("already") && msg_lower.contains("init") {
314 Some("The contract has already been initialized.".into())
315 } else {
316 None
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use chainerrors_core::ErrorDecoder;
324
325 fn decoder() -> EvmErrorDecoder {
326 EvmErrorDecoder::new()
327 }
328
329 #[test]
330 fn decode_empty() {
331 let d = decoder();
332 let result = d.decode(&[], None).unwrap();
333 assert!(matches!(result.kind, ErrorKind::Empty));
334 assert_eq!(result.selector, None);
335 }
336
337 #[test]
338 fn decode_revert_string() {
339 let data = hex::decode(
341 "08c379a0\
342 0000000000000000000000000000000000000000000000000000000000000020\
343 0000000000000000000000000000000000000000000000000000000000000011\
344 4e6f7420656e6f75676820746f6b656e73000000000000000000000000000000",
345 )
346 .unwrap();
347 let result = decoder().decode(&data, None).unwrap();
348 match &result.kind {
349 ErrorKind::RevertString { message } => assert_eq!(message, "Not enough tokens"),
350 _ => panic!("expected RevertString, got {:?}", result.kind),
351 }
352 assert_eq!(result.confidence, 1.0);
353 }
354
355 #[test]
356 fn decode_panic_overflow() {
357 let data = hex::decode(
358 "4e487b710000000000000000000000000000000000000000000000000000000000000011",
359 )
360 .unwrap();
361 let result = decoder().decode(&data, None).unwrap();
362 match &result.kind {
363 ErrorKind::Panic { code, meaning } => {
364 assert_eq!(*code, 0x11);
365 assert!(meaning.contains("overflow"));
366 }
367 _ => panic!("expected Panic, got {:?}", result.kind),
368 }
369 assert_eq!(result.confidence, 1.0);
370 }
371
372 #[test]
373 fn decode_oz_ownable_error() {
374 use tiny_keccak::{Hasher, Keccak};
375 let mut k = Keccak::v256();
377 k.update(b"OwnableUnauthorizedAccount(address)");
378 let mut hash = [0u8; 32];
379 k.finalize(&mut hash);
380 let sel = [hash[0], hash[1], hash[2], hash[3]];
381
382 let mut data = sel.to_vec();
384 data.extend_from_slice(&[0u8; 12]);
385 data.extend_from_slice(&[0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe]);
386
387 let result = decoder().decode(&data, None).unwrap();
388 assert!(
389 matches!(&result.kind, ErrorKind::CustomError { name, .. } if name == "OwnableUnauthorizedAccount"),
390 "got: {:?}", result.kind
391 );
392 assert!(result.suggestion.is_some());
393 }
394
395 #[test]
396 fn decode_raw_revert_unknown() {
397 let data = [0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04];
398 let result = decoder().decode(&data, None).unwrap();
399 assert!(matches!(result.kind, ErrorKind::RawRevert { .. }));
400 assert_eq!(result.confidence, 0.0);
401 }
402
403 #[test]
404 fn decode_hex_helper() {
405 let d = decoder();
406 let result = d
408 .decode_hex(
409 "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000",
410 None,
411 )
412 .unwrap();
413 assert!(matches!(result.kind, ErrorKind::RevertString { ref message } if message == "Hello"));
414 }
415}