chaincodec_evm/
call_decoder.rs1use alloy_core::dyn_abi::{DynSolType, DynSolValue};
11use alloy_dyn_abi::Specifier;
12use alloy_json_abi::{Function, JsonAbi};
13use chaincodec_core::{
14 call::{DecodedCall, DecodedConstructor},
15 error::DecodeError,
16 types::NormalizedValue,
17};
18use std::collections::HashMap;
19
20use crate::normalizer;
21
22pub struct EvmCallDecoder {
27 abi: JsonAbi,
28}
29
30impl EvmCallDecoder {
31 pub fn from_abi_json(abi_json: &str) -> Result<Self, DecodeError> {
36 let abi: JsonAbi = serde_json::from_str(abi_json)
37 .map_err(|e| DecodeError::AbiDecodeFailed {
38 reason: format!("invalid ABI JSON: {e}"),
39 })?;
40 Ok(Self { abi })
41 }
42
43 pub fn decode_call(
52 &self,
53 calldata: &[u8],
54 function_name: Option<&str>,
55 ) -> Result<DecodedCall, DecodeError> {
56 if calldata.len() < 4 {
57 return Err(DecodeError::InvalidRawEvent {
58 reason: format!(
59 "calldata too short: {} bytes (need at least 4 for selector)",
60 calldata.len()
61 ),
62 });
63 }
64
65 let selector: [u8; 4] = calldata[..4].try_into().unwrap();
66 let input_data = &calldata[4..];
67
68 let func = self.find_function(selector, function_name)?;
70
71 let (input_names, input_types) = extract_function_types(&func);
73
74 let decoded_inputs = decode_abi_tuple(input_data, &input_types, &input_names)?;
75
76 Ok(DecodedCall {
77 function_name: func.name.clone(),
78 selector: Some(selector),
79 inputs: decoded_inputs,
80 raw_data: calldata.to_vec(),
81 decode_errors: HashMap::new(),
82 })
83 }
84
85 pub fn decode_constructor(&self, calldata: &[u8]) -> Result<DecodedConstructor, DecodeError> {
90 let constructor = self.abi.constructor().ok_or_else(|| DecodeError::AbiDecodeFailed {
91 reason: "ABI has no constructor definition".into(),
92 })?;
93
94 let input_types: Vec<DynSolType> = constructor
95 .inputs
96 .iter()
97 .map(|p| p.resolve().map_err(|e| DecodeError::AbiDecodeFailed { reason: e.to_string() }))
98 .collect::<Result<Vec<_>, _>>()?;
99
100 let input_names: Vec<String> = constructor
101 .inputs
102 .iter()
103 .enumerate()
104 .map(|(i, p)| {
105 if p.name.is_empty() {
106 format!("arg{i}")
107 } else {
108 p.name.clone()
109 }
110 })
111 .collect();
112
113 let decoded_args = decode_abi_tuple(calldata, &input_types, &input_names)?;
114
115 Ok(DecodedConstructor {
116 args: decoded_args,
117 raw_data: calldata.to_vec(),
118 decode_errors: HashMap::new(),
119 })
120 }
121
122 fn find_function(
124 &self,
125 selector: [u8; 4],
126 name_hint: Option<&str>,
127 ) -> Result<&Function, DecodeError> {
128 if let Some(name) = name_hint {
130 for func in self.abi.functions() {
131 if func.name == name && func.selector() == selector {
132 return Ok(func);
133 }
134 }
135 for func in self.abi.functions() {
137 if func.name == name {
138 return Ok(func);
139 }
140 }
141 return Err(DecodeError::SchemaNotFound {
142 fingerprint: format!("function '{}' not found in ABI", name),
143 });
144 }
145
146 for func in self.abi.functions() {
148 if func.selector() == selector {
149 return Ok(func);
150 }
151 }
152
153 Err(DecodeError::SchemaNotFound {
154 fingerprint: format!(
155 "no function found for selector 0x{}",
156 hex::encode(selector)
157 ),
158 })
159 }
160
161 pub fn function_names(&self) -> Vec<&str> {
163 self.abi.functions().map(|f| f.name.as_str()).collect()
164 }
165
166 pub fn selector_for(&self, name: &str) -> Option<[u8; 4]> {
168 self.abi
169 .functions()
170 .find(|f| f.name == name)
171 .map(|f| f.selector().0)
172 }
173}
174
175fn extract_function_types(func: &Function) -> (Vec<String>, Vec<DynSolType>) {
177 let mut names = Vec::new();
178 let mut types = Vec::new();
179
180 for (i, param) in func.inputs.iter().enumerate() {
181 let name = if param.name.is_empty() {
182 format!("arg{i}")
183 } else {
184 param.name.clone()
185 };
186 names.push(name);
187 if let Ok(ty) = param.resolve() {
189 types.push(ty);
190 }
191 }
192
193 (names, types)
194}
195
196fn decode_abi_tuple(
198 data: &[u8],
199 types: &[DynSolType],
200 names: &[String],
201) -> Result<Vec<(String, NormalizedValue)>, DecodeError> {
202 if types.is_empty() {
203 return Ok(vec![]);
204 }
205
206 let tuple_type = DynSolType::Tuple(types.to_vec());
207 let decoded = tuple_type
208 .abi_decode(data)
209 .map_err(|e| DecodeError::AbiDecodeFailed {
210 reason: format!("function input decode: {e}"),
211 })?;
212
213 let values = match decoded {
214 DynSolValue::Tuple(vals) => vals,
215 other => vec![other],
216 };
217
218 let result: Vec<(String, NormalizedValue)> = names
219 .iter()
220 .zip(values.into_iter())
221 .map(|(name, val)| (name.clone(), normalizer::normalize(val)))
222 .collect();
223
224 Ok(result)
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 const ERC20_ABI: &str = r#"[
233 {
234 "name": "transfer",
235 "type": "function",
236 "inputs": [
237 {"name": "to", "type": "address"},
238 {"name": "amount", "type": "uint256"}
239 ],
240 "outputs": [{"name": "", "type": "bool"}],
241 "stateMutability": "nonpayable"
242 },
243 {
244 "name": "approve",
245 "type": "function",
246 "inputs": [
247 {"name": "spender", "type": "address"},
248 {"name": "amount", "type": "uint256"}
249 ],
250 "outputs": [{"name": "", "type": "bool"}],
251 "stateMutability": "nonpayable"
252 }
253 ]"#;
254
255 #[test]
256 fn decoder_parses_abi_json() {
257 let dec = EvmCallDecoder::from_abi_json(ERC20_ABI).unwrap();
258 let names = dec.function_names();
259 assert!(names.contains(&"transfer"));
260 assert!(names.contains(&"approve"));
261 }
262
263 #[test]
264 fn selector_for_transfer() {
265 let dec = EvmCallDecoder::from_abi_json(ERC20_ABI).unwrap();
266 let sel = dec.selector_for("transfer").unwrap();
268 assert_eq!(hex::encode(sel), "a9059cbb");
269 }
270
271 #[test]
272 fn decode_transfer_calldata() {
273 let dec = EvmCallDecoder::from_abi_json(ERC20_ABI).unwrap();
274
275 let selector = hex::decode("a9059cbb").unwrap();
278 let to = hex::decode(
280 "000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045",
281 )
282 .unwrap();
283 let amount = hex::decode(
285 "00000000000000000000000000000000000000000000000000000000000f4240",
286 )
287 .unwrap();
288 let mut calldata = selector;
289 calldata.extend_from_slice(&to);
290 calldata.extend_from_slice(&amount);
291
292 let result = dec.decode_call(&calldata, None).unwrap();
293 assert_eq!(result.function_name, "transfer");
294 assert!(result.is_clean());
295 assert_eq!(result.inputs.len(), 2);
296 assert_eq!(result.inputs[0].0, "to");
297 assert_eq!(result.inputs[1].0, "amount");
298
299 if let NormalizedValue::BigUint(v) = &result.inputs[1].1 {
301 assert_eq!(v.parse::<u128>().unwrap(), 1_000_000u128);
302 } else {
303 panic!("expected BigUint for uint256 amount, got {:?}", result.inputs[1].1);
304 }
305 }
306
307 #[test]
308 fn invalid_json_returns_error() {
309 let result = EvmCallDecoder::from_abi_json("not json");
310 assert!(result.is_err());
311 }
312}