Skip to main content

chainerrors_evm/
custom.rs

1//! Decode Solidity 0.8.4+ custom errors.
2//!
3//! Custom errors are ABI-encoded as:
4//! `selector(4 bytes)` ++ `ABI-encoded arguments`
5//!
6//! Where `selector = keccak256("ErrorName(type1,type2,...)")[:4]`
7
8use alloy_core::dyn_abi::{DynSolType, DynSolValue};
9use chainerrors_core::registry::{ErrorSignature, ErrorSignatureRegistry};
10use chainerrors_core::types::{ErrorFieldValue, ErrorKind};
11
12/// Try to decode custom error data against a known registry.
13///
14/// Returns `Some(ErrorKind::CustomError { .. })` if the selector matches a
15/// registry entry and the ABI decode succeeds. Returns `None` otherwise.
16pub fn decode_custom_error(
17    data: &[u8],
18    registry: &dyn ErrorSignatureRegistry,
19) -> Option<ErrorKind> {
20    if data.len() < 4 {
21        return None;
22    }
23    let selector: [u8; 4] = data[..4].try_into().ok()?;
24    let sigs = registry.get_by_selector(selector);
25    if sigs.is_empty() {
26        return None;
27    }
28    let payload = &data[4..];
29    // Try each matching signature (handle rare selector collisions)
30    for sig in &sigs {
31        if let Some(kind) = try_decode_with_signature(sig, payload) {
32            return Some(kind);
33        }
34    }
35    None
36}
37
38fn try_decode_with_signature(sig: &ErrorSignature, payload: &[u8]) -> Option<ErrorKind> {
39    if sig.inputs.is_empty() {
40        // No arguments — just return the name
41        return Some(ErrorKind::CustomError {
42            name: sig.name.clone(),
43            inputs: vec![],
44        });
45    }
46
47    // Build alloy DynSolType for each input
48    let types: Vec<DynSolType> = sig
49        .inputs
50        .iter()
51        .map(|p| p.ty.parse::<DynSolType>().ok())
52        .collect::<Option<Vec<_>>>()?;
53
54    // Decode as a tuple
55    let tuple_type = DynSolType::Tuple(types);
56    let decoded = tuple_type.abi_decode(payload).ok()?;
57
58    let values = match decoded {
59        DynSolValue::Tuple(vals) => vals,
60        single => vec![single],
61    };
62
63    let inputs: Vec<(String, ErrorFieldValue)> = sig
64        .inputs
65        .iter()
66        .zip(values.iter())
67        .map(|(param, val)| (param.name.clone(), dyn_sol_to_field_value(val)))
68        .collect();
69
70    Some(ErrorKind::CustomError {
71        name: sig.name.clone(),
72        inputs,
73    })
74}
75
76fn dyn_sol_to_field_value(val: &DynSolValue) -> ErrorFieldValue {
77    match val {
78        DynSolValue::Uint(v, _) => {
79            let s = v.to_string();
80            if let Ok(small) = s.parse::<u128>() {
81                ErrorFieldValue::Uint(small)
82            } else {
83                ErrorFieldValue::BigUint(s)
84            }
85        }
86        DynSolValue::Int(v, _) => {
87            let s = v.to_string();
88            if let Ok(i) = s.parse::<i128>() {
89                ErrorFieldValue::Int(i)
90            } else {
91                ErrorFieldValue::BigInt(s)
92            }
93        }
94        DynSolValue::Bool(b) => ErrorFieldValue::Bool(*b),
95        DynSolValue::Address(a) => ErrorFieldValue::Address(format!("{a:#x}")),
96        DynSolValue::String(s) => ErrorFieldValue::Str(s.clone()),
97        DynSolValue::Bytes(b) => ErrorFieldValue::Bytes(b.clone()),
98        DynSolValue::FixedBytes(fb, _) => ErrorFieldValue::Bytes(fb.to_vec()),
99        _ => ErrorFieldValue::Bytes(vec![]),
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use chainerrors_core::registry::{ErrorParam, ErrorSignature, MemoryErrorRegistry};
107    use tiny_keccak::{Hasher, Keccak};
108
109    fn selector_of(sig: &str) -> [u8; 4] {
110        let mut k = Keccak::v256();
111        k.update(sig.as_bytes());
112        let mut out = [0u8; 32];
113        k.finalize(&mut out);
114        [out[0], out[1], out[2], out[3]]
115    }
116
117    fn make_reg(name: &str, sig_str: &str, inputs: Vec<ErrorParam>) -> MemoryErrorRegistry {
118        let reg = MemoryErrorRegistry::new();
119        reg.register(ErrorSignature {
120            name: name.to_string(),
121            signature: sig_str.to_string(),
122            selector: selector_of(sig_str),
123            inputs,
124            source: "test".to_string(),
125            suggestion: None,
126        });
127        reg
128    }
129
130    #[test]
131    fn decode_oz_ownable_unauthorized() {
132        // error OwnableUnauthorizedAccount(address account)
133        // selector = keccak256("OwnableUnauthorizedAccount(address)")[..4]
134        let sig_str = "OwnableUnauthorizedAccount(address)";
135        let reg = make_reg(
136            "OwnableUnauthorizedAccount",
137            sig_str,
138            vec![ErrorParam { name: "account".into(), ty: "address".into() }],
139        );
140        let sel = selector_of(sig_str);
141
142        // Encode: selector ++ address (20 bytes, zero-padded to 32)
143        let addr_bytes = hex::decode("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").unwrap();
144        let mut data = sel.to_vec();
145        data.extend_from_slice(&[0u8; 12]); // left-pad address to 32 bytes
146        data.extend_from_slice(&addr_bytes);
147
148        let kind = decode_custom_error(&data, &reg).unwrap();
149        match kind {
150            ErrorKind::CustomError { name, inputs } => {
151                assert_eq!(name, "OwnableUnauthorizedAccount");
152                assert_eq!(inputs.len(), 1);
153                assert_eq!(inputs[0].0, "account");
154            }
155            _ => panic!("unexpected kind: {kind:?}"),
156        }
157    }
158
159    #[test]
160    fn decode_custom_error_no_args() {
161        // error T() — Uniswap V3 style zero-arg custom error
162        let sig_str = "T()";
163        let reg = make_reg("T", sig_str, vec![]);
164        let sel = selector_of(sig_str);
165
166        let kind = decode_custom_error(&sel, &reg).unwrap();
167        assert!(matches!(kind, ErrorKind::CustomError { ref name, .. } if name == "T"));
168    }
169
170    #[test]
171    fn decode_unknown_selector_returns_none() {
172        let reg = MemoryErrorRegistry::new();
173        let data = [0xde, 0xad, 0xbe, 0xef, 0x00, 0x00];
174        assert!(decode_custom_error(&data, &reg).is_none());
175    }
176}