Skip to main content

chaincodec_evm/
call_decoder.rs

1//! EVM function-call and constructor calldata decoder.
2//!
3//! Decodes transaction `input` data using an ABI JSON definition.
4//!
5//! # How it works
6//! - First 4 bytes of calldata = keccak256(function_signature)[:4] (the selector)
7//! - Remaining bytes = ABI-encoded inputs tuple
8//! - Constructor: no selector prefix; all bytes = ABI-encoded constructor args
9
10use 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
22/// EVM function-call decoder.
23///
24/// Accepts an ABI JSON string (standard Ethereum ABI JSON format) and decodes
25/// raw calldata into structured `DecodedCall` or `DecodedConstructor` results.
26pub struct EvmCallDecoder {
27    abi: JsonAbi,
28}
29
30impl EvmCallDecoder {
31    /// Create a decoder from a standard Ethereum ABI JSON string.
32    ///
33    /// # Errors
34    /// Returns `DecodeError` if the JSON is not valid ABI JSON.
35    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    /// Decode a function call from raw calldata bytes.
44    ///
45    /// If `function_name` is provided, the selector is validated against that
46    /// function. Otherwise the selector is matched against all functions in the ABI.
47    ///
48    /// # Arguments
49    /// * `calldata` - full calldata including the 4-byte selector prefix
50    /// * `function_name` - optional hint to match a specific function
51    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        // Find the matching function
69        let func = self.find_function(selector, function_name)?;
70
71        // Build tuple type from function inputs
72        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    /// Decode constructor calldata (no selector prefix).
86    ///
87    /// # Arguments
88    /// * `calldata` - raw constructor arguments (ABI-encoded, no 4-byte prefix)
89    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    /// Find a function by selector, optionally constrained to a specific name.
123    fn find_function(
124        &self,
125        selector: [u8; 4],
126        name_hint: Option<&str>,
127    ) -> Result<&Function, DecodeError> {
128        // If name hint given, search by name first
129        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            // Try name match ignoring selector mismatch (useful for overloaded fns)
136            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        // Match by selector alone
147        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    /// Returns all function names in this ABI.
162    pub fn function_names(&self) -> Vec<&str> {
163        self.abi.functions().map(|f| f.name.as_str()).collect()
164    }
165
166    /// Returns the 4-byte selector for a named function.
167    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
175/// Extract (names, DynSolTypes) from a function's inputs.
176fn 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        // Best-effort resolve; skip unresolvable types
188        if let Ok(ty) = param.resolve() {
189            types.push(ty);
190        }
191    }
192
193    (names, types)
194}
195
196/// ABI-decode a tuple of types and pair with names → NormalizedValue.
197fn 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    // Standard ERC-20 ABI (transfer function only for brevity)
232    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        // keccak256("transfer(address,uint256)")[:4] = 0xa9059cbb
267        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        // Build real calldata: selector + ABI-encoded (address, uint256)
276        // transfer(to=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, amount=1000000)
277        let selector = hex::decode("a9059cbb").unwrap();
278        // address padded to 32 bytes
279        let to = hex::decode(
280            "000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045",
281        )
282        .unwrap();
283        // uint256 = 1000000
284        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        // Amount should decode to 1000000
300        if let NormalizedValue::Uint(v) = &result.inputs[1].1 {
301            assert_eq!(*v, 1_000_000u128);
302        } else {
303            panic!("expected Uint for amount");
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}