Skip to main content

chainerrors_evm/
revert.rs

1//! Decode `Error(string)` revert strings.
2//!
3//! EVM encodes `require(cond, "message")` as:
4//! `0x08c379a0` ++ ABI-encode(string)
5//!
6//! This selector is `keccak256("Error(string)")[..4]`.
7
8use alloy_core::dyn_abi::{DynSolValue, DynSolType};
9
10/// The 4-byte selector for `Error(string)`.
11pub const ERROR_STRING_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0];
12
13/// Try to decode the revert data as an `Error(string)` payload.
14///
15/// Returns `Some(message)` on success, `None` if the data doesn't match
16/// the expected format.
17pub fn decode_error_string(data: &[u8]) -> Option<String> {
18    if data.len() < 4 {
19        return None;
20    }
21    if &data[..4] != ERROR_STRING_SELECTOR {
22        return None;
23    }
24    let payload = &data[4..];
25    // ABI-decode as a single `string` type
26    let ty = DynSolType::String;
27    match ty.abi_decode(payload) {
28        Ok(DynSolValue::String(s)) => Some(s.trim_end_matches('\0').to_string()),
29        _ => None,
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    /// Hex from `require(false, "Not enough tokens to transfer")` on mainnet
38    const REVERT_HEX: &str = "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e4e6f7420656e6f75676820746f6b656e7320746f207472616e73666572000000";
39
40    #[test]
41    fn decode_error_string_basic() {
42        let data = hex::decode(REVERT_HEX).unwrap();
43        let msg = decode_error_string(&data).unwrap();
44        assert_eq!(msg, "Not enough tokens to transfer");
45    }
46
47    #[test]
48    fn decode_error_string_wrong_selector() {
49        let data = hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000011").unwrap();
50        assert!(decode_error_string(&data).is_none());
51    }
52
53    #[test]
54    fn decode_error_string_too_short() {
55        assert!(decode_error_string(&[0x08, 0xc3]).is_none());
56    }
57
58    #[test]
59    fn decode_error_string_empty_message() {
60        // Error("") — ABI-encoded empty string
61        let data = hex::decode(
62            "08c379a0\
63             0000000000000000000000000000000000000000000000000000000000000020\
64             0000000000000000000000000000000000000000000000000000000000000000",
65        )
66        .unwrap();
67        let msg = decode_error_string(&data).unwrap();
68        assert_eq!(msg, "");
69    }
70}