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 (NormalizedValue::Uint(u), DynSolType::Uint(bits)) => {
190 Ok(DynSolValue::Uint(U256::from(*u), *bits))
191 }
192
193 _ => Err(format!(
194 "cannot convert {:?} to {:?}",
195 std::mem::discriminant(val),
196 expected
197 )),
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 const ERC20_ABI: &str = r#"[
206 {
207 "name": "transfer",
208 "type": "function",
209 "inputs": [
210 {"name": "to", "type": "address"},
211 {"name": "amount", "type": "uint256"}
212 ],
213 "outputs": [{"name": "", "type": "bool"}],
214 "stateMutability": "nonpayable"
215 }
216 ]"#;
217
218 #[test]
219 fn encode_transfer() {
220 let encoder = EvmEncoder::from_abi_json(ERC20_ABI).unwrap();
221 let calldata = encoder
222 .encode_call(
223 "transfer",
224 &[
225 NormalizedValue::Address(
226 "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".into(),
227 ),
228 NormalizedValue::Uint(1_000_000),
229 ],
230 )
231 .unwrap();
232
233 assert_eq!(&calldata[..4], hex::decode("a9059cbb").unwrap().as_slice());
235 assert_eq!(calldata.len(), 68);
237 }
238
239 #[test]
240 fn wrong_arg_count_returns_error() {
241 let encoder = EvmEncoder::from_abi_json(ERC20_ABI).unwrap();
242 let result = encoder.encode_call("transfer", &[NormalizedValue::Uint(1)]);
243 assert!(result.is_err());
244 }
245
246 #[test]
247 fn roundtrip_encode_decode() {
248 use crate::call_decoder::EvmCallDecoder;
249
250 let encoder = EvmEncoder::from_abi_json(ERC20_ABI).unwrap();
251 let decoder = EvmCallDecoder::from_abi_json(ERC20_ABI).unwrap();
252
253 let original_to = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
254 let original_amount: u128 = 999_888;
255
256 let calldata = encoder
257 .encode_call(
258 "transfer",
259 &[
260 NormalizedValue::Address(original_to.to_lowercase()),
261 NormalizedValue::Uint(original_amount),
262 ],
263 )
264 .unwrap();
265
266 let decoded = decoder.decode_call(&calldata, None).unwrap();
267 assert_eq!(decoded.function_name, "transfer");
268
269 if let NormalizedValue::Uint(amount) = &decoded.inputs[1].1 {
270 assert_eq!(*amount, original_amount);
271 } else {
272 panic!("expected Uint for amount");
273 }
274 }
275}