1use alloy_dyn_abi::Specifier;
16use alloy_core::dyn_abi::{DynSolType, DynSolValue};
17use alloy_json_abi::JsonAbi;
18use alloy_primitives::{Address, FixedBytes, I256, U256};
19use chaincodec_core::{error::DecodeError, types::NormalizedValue};
20use std::str::FromStr;
21
22pub struct EvmEncoder {
24 abi: JsonAbi,
25}
26
27impl EvmEncoder {
28 pub fn from_abi_json(abi_json: &str) -> Result<Self, DecodeError> {
30 let abi: JsonAbi = serde_json::from_str(abi_json)
31 .map_err(|e| DecodeError::AbiDecodeFailed {
32 reason: format!("invalid ABI JSON: {e}"),
33 })?;
34 Ok(Self { abi })
35 }
36
37 pub fn encode_call(
46 &self,
47 function_name: &str,
48 args: &[NormalizedValue],
49 ) -> Result<Vec<u8>, DecodeError> {
50 let func = self
51 .abi
52 .functions()
53 .find(|f| f.name == function_name)
54 .ok_or_else(|| DecodeError::SchemaNotFound {
55 fingerprint: format!("function '{function_name}' not found in ABI"),
56 })?;
57
58 if args.len() != func.inputs.len() {
59 return Err(DecodeError::AbiDecodeFailed {
60 reason: format!(
61 "argument count mismatch: ABI has {}, got {}",
62 func.inputs.len(),
63 args.len()
64 ),
65 });
66 }
67
68 let mut dyn_values = Vec::with_capacity(args.len());
69 for (i, (param, arg)) in func.inputs.iter().zip(args.iter()).enumerate() {
70 let sol_type = param.resolve().map_err(|e| DecodeError::AbiDecodeFailed {
71 reason: format!("param {i}: {e}"),
72 })?;
73 let dyn_val = normalized_to_dyn_value(arg, &sol_type).map_err(|e| {
74 DecodeError::AbiDecodeFailed {
75 reason: format!("param '{}': {e}", param.name),
76 }
77 })?;
78 dyn_values.push(dyn_val);
79 }
80
81 let selector = func.selector();
83
84 let encoded = DynSolValue::Tuple(dyn_values).abi_encode();
86
87 let mut calldata = selector.to_vec();
88 calldata.extend_from_slice(&encoded);
89 Ok(calldata)
90 }
91
92 pub fn encode_tuple(
96 &self,
97 type_strings: &[&str],
98 args: &[NormalizedValue],
99 ) -> Result<Vec<u8>, DecodeError> {
100 if type_strings.len() != args.len() {
101 return Err(DecodeError::AbiDecodeFailed {
102 reason: "type_strings and args length mismatch".into(),
103 });
104 }
105
106 let mut dyn_values = Vec::new();
107 for (ty_str, arg) in type_strings.iter().zip(args.iter()) {
108 let sol_type: DynSolType = ty_str.parse().map_err(|e: alloy_core::dyn_abi::Error| {
109 DecodeError::AbiDecodeFailed {
110 reason: format!("type parse '{ty_str}': {e}"),
111 }
112 })?;
113 let dyn_val = normalized_to_dyn_value(arg, &sol_type)
114 .map_err(|e| DecodeError::AbiDecodeFailed { reason: e })?;
115 dyn_values.push(dyn_val);
116 }
117
118 Ok(DynSolValue::Tuple(dyn_values).abi_encode())
119 }
120}
121
122pub fn normalized_to_dyn_value(
124 val: &NormalizedValue,
125 expected: &DynSolType,
126) -> Result<DynSolValue, String> {
127 match (val, expected) {
128 (NormalizedValue::Bool(b), DynSolType::Bool) => Ok(DynSolValue::Bool(*b)),
129
130 (NormalizedValue::Uint(u), DynSolType::Uint(bits)) => {
131 Ok(DynSolValue::Uint(U256::from(*u), *bits))
132 }
133 (NormalizedValue::BigUint(s), DynSolType::Uint(bits)) => {
134 let u = U256::from_str(s).map_err(|e| format!("BigUint parse: {e}"))?;
135 Ok(DynSolValue::Uint(u, *bits))
136 }
137
138 (NormalizedValue::Int(i), DynSolType::Int(bits)) => {
139 Ok(DynSolValue::Int(I256::try_from(*i).map_err(|e| e.to_string())?, *bits))
140 }
141 (NormalizedValue::BigInt(s), DynSolType::Int(bits)) => {
142 let i = I256::from_str(s).map_err(|e| format!("BigInt parse: {e}"))?;
143 Ok(DynSolValue::Int(i, *bits))
144 }
145
146 (NormalizedValue::Address(s), DynSolType::Address) => {
147 let addr = Address::from_str(s).map_err(|e| format!("address parse: {e}"))?;
148 Ok(DynSolValue::Address(addr))
149 }
150
151 (NormalizedValue::Bytes(b), DynSolType::Bytes) => Ok(DynSolValue::Bytes(b.clone())),
152
153 (NormalizedValue::Bytes(b), DynSolType::FixedBytes(n)) => {
154 if b.len() > *n {
155 return Err(format!("bytes{n}: got {} bytes", b.len()));
156 }
157 let mut arr = [0u8; 32];
158 arr[..*n.min(&b.len())].copy_from_slice(&b[..*n.min(&b.len())]);
159 Ok(DynSolValue::FixedBytes(FixedBytes::from_slice(&arr[..*n]), *n))
160 }
161
162 (NormalizedValue::Str(s), DynSolType::String) => Ok(DynSolValue::String(s.clone())),
163
164 (NormalizedValue::Array(elems), DynSolType::Array(inner)) => {
165 let dyn_elems: Result<Vec<_>, _> =
166 elems.iter().map(|e| normalized_to_dyn_value(e, inner)).collect();
167 Ok(DynSolValue::Array(dyn_elems?))
168 }
169
170 (NormalizedValue::Array(elems), DynSolType::FixedArray(inner, len)) => {
171 if elems.len() != *len {
172 return Err(format!("fixed array length mismatch: expected {len}, got {}", elems.len()));
173 }
174 let dyn_elems: Result<Vec<_>, _> =
175 elems.iter().map(|e| normalized_to_dyn_value(e, inner)).collect();
176 Ok(DynSolValue::FixedArray(dyn_elems?))
177 }
178
179 (NormalizedValue::Tuple(fields), DynSolType::Tuple(types)) => {
180 let dyn_elems: Result<Vec<_>, _> = fields
181 .iter()
182 .zip(types.iter())
183 .map(|((_, v), t)| normalized_to_dyn_value(v, t))
184 .collect();
185 Ok(DynSolValue::Tuple(dyn_elems?))
186 }
187
188 _ => Err(format!(
189 "cannot convert {:?} to {:?}",
190 std::mem::discriminant(val),
191 expected
192 )),
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 const ERC20_ABI: &str = r#"[
201 {
202 "name": "transfer",
203 "type": "function",
204 "inputs": [
205 {"name": "to", "type": "address"},
206 {"name": "amount", "type": "uint256"}
207 ],
208 "outputs": [{"name": "", "type": "bool"}],
209 "stateMutability": "nonpayable"
210 }
211 ]"#;
212
213 #[test]
214 fn encode_transfer() {
215 let encoder = EvmEncoder::from_abi_json(ERC20_ABI).unwrap();
216 let calldata = encoder
217 .encode_call(
218 "transfer",
219 &[
220 NormalizedValue::Address(
221 "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".into(),
222 ),
223 NormalizedValue::Uint(1_000_000),
224 ],
225 )
226 .unwrap();
227
228 assert_eq!(&calldata[..4], hex::decode("a9059cbb").unwrap().as_slice());
230 assert_eq!(calldata.len(), 68);
232 }
233
234 #[test]
235 fn wrong_arg_count_returns_error() {
236 let encoder = EvmEncoder::from_abi_json(ERC20_ABI).unwrap();
237 let result = encoder.encode_call("transfer", &[NormalizedValue::Uint(1)]);
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn roundtrip_encode_decode() {
243 use crate::call_decoder::EvmCallDecoder;
244
245 let encoder = EvmEncoder::from_abi_json(ERC20_ABI).unwrap();
246 let decoder = EvmCallDecoder::from_abi_json(ERC20_ABI).unwrap();
247
248 let original_to = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
249 let original_amount: u128 = 999_888;
250
251 let calldata = encoder
252 .encode_call(
253 "transfer",
254 &[
255 NormalizedValue::Address(original_to.to_lowercase()),
256 NormalizedValue::Uint(original_amount),
257 ],
258 )
259 .unwrap();
260
261 let decoded = decoder.decode_call(&calldata, None).unwrap();
262 assert_eq!(decoded.function_name, "transfer");
263
264 if let NormalizedValue::Uint(amount) = &decoded.inputs[1].1 {
265 assert_eq!(*amount, original_amount);
266 } else {
267 panic!("expected Uint for amount");
268 }
269 }
270}