Skip to main content

chainerrors_evm/
panic.rs

1//! Decode Solidity `Panic(uint256)` errors (introduced in Solidity 0.8.0).
2//!
3//! Selector: `0x4e487b71` == `keccak256("Panic(uint256)")[..4]`
4//!
5//! Full list of panic codes:
6//! <https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require>
7
8use alloy_core::dyn_abi::{DynSolType, DynSolValue};
9
10/// The 4-byte selector for `Panic(uint256)`.
11pub const PANIC_SELECTOR: [u8; 4] = [0x4e, 0x48, 0x7b, 0x71];
12
13/// Decode `Panic(uint256)` revert data.
14///
15/// Returns `Some((code, meaning))` on success, `None` if not a panic revert.
16pub fn decode_panic(data: &[u8]) -> Option<(u64, &'static str)> {
17    if data.len() < 4 {
18        return None;
19    }
20    if &data[..4] != PANIC_SELECTOR {
21        return None;
22    }
23    let payload = &data[4..];
24    let ty = DynSolType::Uint(256);
25    match ty.abi_decode(payload) {
26        Ok(DynSolValue::Uint(v, _)) => {
27            let code = v.to::<u64>();
28            Some((code, panic_meaning(code)))
29        }
30        _ => None,
31    }
32}
33
34/// Map a Solidity panic code to a human-readable description.
35pub fn panic_meaning(code: u64) -> &'static str {
36    match code {
37        0x00 => "generic compiler-inserted panic",
38        0x01 => "assert() called with false condition",
39        0x11 => "arithmetic overflow or underflow",
40        0x12 => "division or modulo by zero",
41        0x21 => "invalid enum value",
42        0x22 => "corrupted storage byte array",
43        0x31 => ".pop() on empty array",
44        0x32 => "out-of-bounds array access",
45        0x41 => "too much memory allocated (out of memory)",
46        0x51 => "called zero-initialized internal function pointer",
47        _ => "unknown panic code",
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    /// `Panic(0x11)` — arithmetic overflow
56    const PANIC_OVERFLOW_HEX: &str =
57        "4e487b710000000000000000000000000000000000000000000000000000000000000011";
58
59    /// `Panic(0x12)` — division by zero
60    const PANIC_DIV_ZERO_HEX: &str =
61        "4e487b710000000000000000000000000000000000000000000000000000000000000012";
62
63    #[test]
64    fn decode_panic_overflow() {
65        let data = hex::decode(PANIC_OVERFLOW_HEX).unwrap();
66        let (code, meaning) = decode_panic(&data).unwrap();
67        assert_eq!(code, 0x11);
68        assert!(meaning.contains("overflow"));
69    }
70
71    #[test]
72    fn decode_panic_div_zero() {
73        let data = hex::decode(PANIC_DIV_ZERO_HEX).unwrap();
74        let (code, meaning) = decode_panic(&data).unwrap();
75        assert_eq!(code, 0x12);
76        assert!(meaning.contains("division"));
77    }
78
79    #[test]
80    fn decode_panic_wrong_selector() {
81        let data = hex::decode("08c379a000").unwrap();
82        assert!(decode_panic(&data).is_none());
83    }
84
85    #[test]
86    fn panic_meaning_known_codes() {
87        assert_eq!(panic_meaning(0x01), "assert() called with false condition");
88        assert_eq!(panic_meaning(0x32), "out-of-bounds array access");
89        assert_eq!(panic_meaning(0x99), "unknown panic code");
90    }
91}