chainerrors_evm/
custom.rs1use alloy_core::dyn_abi::{DynSolType, DynSolValue};
9use chainerrors_core::registry::{ErrorSignature, ErrorSignatureRegistry};
10use chainerrors_core::types::{ErrorFieldValue, ErrorKind};
11
12pub 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 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 return Some(ErrorKind::CustomError {
42 name: sig.name.clone(),
43 inputs: vec![],
44 });
45 }
46
47 let types: Vec<DynSolType> = sig
49 .inputs
50 .iter()
51 .map(|p| p.ty.parse::<DynSolType>().ok())
52 .collect::<Option<Vec<_>>>()?;
53
54 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 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 let addr_bytes = hex::decode("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").unwrap();
144 let mut data = sel.to_vec();
145 data.extend_from_slice(&[0u8; 12]); data.extend_from_slice(&addr_bytes);
147
148 let kind = decode_custom_error(&data, ®).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 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, ®).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, ®).is_none());
175 }
176}