Skip to main content

chains_sdk/ethereum/
abi.rs

1//! **Ethereum ABI** encoding, decoding, and contract interaction helpers.
2//!
3//! Implements the Solidity ABI spec for encoding function calls, decoding return values,
4//! computing function selectors, and building contract deployment transactions.
5//!
6//! # Supported Types
7//! - `uint8` through `uint256` (by 8-bit increments)
8//! - `int8` through `int256`
9//! - `address` (20 bytes, left-padded to 32)
10//! - `bool`
11//! - `bytes` (dynamic)
12//! - `bytes1` through `bytes32` (fixed)
13//! - `string` (dynamic, UTF-8)
14//! - `T[]` (dynamic array) — via `AbiValue::Array`
15//! - `(T1, T2, ...)` (tuple) — via `AbiValue::Tuple`
16//!
17//! # Example
18//! ```no_run
19//! use chains_sdk::ethereum::abi::{Function, AbiValue};
20//!
21//! let func = Function::new("transfer(address,uint256)");
22//! let calldata = func.encode(&[
23//!     AbiValue::Address([0xAA; 20]),
24//!     AbiValue::Uint256([0; 32]),  // amount as 32-byte big-endian
25//! ]);
26//! ```
27
28use crate::error::SignerError;
29
30// ─── ABI Values ────────────────────────────────────────────────────
31
32/// An ABI-encoded value.
33#[derive(Debug, Clone, PartialEq)]
34pub enum AbiValue {
35    /// `uint<N>` — stored as 32-byte big-endian, zero-padded on the left.
36    Uint256([u8; 32]),
37    /// `int<N>` — stored as 32-byte big-endian, sign-extended.
38    Int256([u8; 32]),
39    /// `address` — 20 bytes.
40    Address([u8; 20]),
41    /// `bool` — true or false.
42    Bool(bool),
43    /// `bytes<N>` (1–32) — fixed-size, right-padded.
44    FixedBytes(Vec<u8>),
45    /// `bytes` — dynamic byte array.
46    Bytes(Vec<u8>),
47    /// `string` — dynamic UTF-8 string.
48    String(String),
49    /// `T[]` — dynamic array of values.
50    Array(Vec<AbiValue>),
51    /// `(T1, T2, ...)` — tuple of values.
52    Tuple(Vec<AbiValue>),
53}
54
55impl AbiValue {
56    /// Create a `uint256` from a `u64`.
57    #[must_use]
58    pub fn from_u64(val: u64) -> Self {
59        let mut buf = [0u8; 32];
60        buf[24..].copy_from_slice(&val.to_be_bytes());
61        AbiValue::Uint256(buf)
62    }
63
64    /// Create a `uint256` from a `u128`.
65    #[must_use]
66    pub fn from_u128(val: u128) -> Self {
67        let mut buf = [0u8; 32];
68        buf[16..].copy_from_slice(&val.to_be_bytes());
69        AbiValue::Uint256(buf)
70    }
71
72    /// Whether this type is dynamic in the ABI encoding sense.
73    fn is_dynamic(&self) -> bool {
74        matches!(
75            self,
76            AbiValue::Bytes(_) | AbiValue::String(_) | AbiValue::Array(_)
77        ) || matches!(self, AbiValue::Tuple(items) if items.iter().any(|i| i.is_dynamic()))
78    }
79
80    /// Encode this value into the head (fixed) part.
81    fn encode_head(&self) -> Vec<u8> {
82        match self {
83            AbiValue::Uint256(val) => val.to_vec(),
84            AbiValue::Int256(val) => val.to_vec(),
85            AbiValue::Address(addr) => {
86                let mut buf = [0u8; 32];
87                buf[12..].copy_from_slice(addr);
88                buf.to_vec()
89            }
90            AbiValue::Bool(b) => {
91                let mut buf = [0u8; 32];
92                buf[31] = if *b { 1 } else { 0 };
93                buf.to_vec()
94            }
95            AbiValue::FixedBytes(data) => {
96                let mut buf = [0u8; 32];
97                let len = data.len().min(32);
98                buf[..len].copy_from_slice(&data[..len]);
99                buf.to_vec()
100            }
101            // Dynamic types: head is a placeholder offset (filled by caller)
102            _ => vec![0u8; 32],
103        }
104    }
105
106    /// Encode this value into the tail (dynamic) part.
107    fn encode_tail(&self) -> Vec<u8> {
108        match self {
109            AbiValue::Bytes(data) => encode_dynamic_bytes(data),
110            AbiValue::String(s) => encode_dynamic_bytes(s.as_bytes()),
111            AbiValue::Array(items) => {
112                let mut buf = Vec::new();
113                // Length prefix
114                let mut len = [0u8; 32];
115                len[24..].copy_from_slice(&(items.len() as u64).to_be_bytes());
116                buf.extend_from_slice(&len);
117                // Encode items as a tuple
118                buf.extend_from_slice(&encode_tuple(items));
119                buf
120            }
121            AbiValue::Tuple(items) => encode_tuple(items),
122            _ => vec![], // static types have no tail
123        }
124    }
125}
126
127// ─── Core Encoding ─────────────────────────────────────────────────
128
129/// ABI-encode a list of values (as a tuple).
130///
131/// This is equivalent to Solidity's `abi.encode(v1, v2, ...)`.
132pub fn encode(values: &[AbiValue]) -> Vec<u8> {
133    encode_tuple(values)
134}
135
136/// ABI-encode values with packed encoding (no padding).
137///
138/// This is equivalent to Solidity's `abi.encodePacked(v1, v2, ...)`.
139/// Warning: packed encoding is **not** decodable and should only be used for hashing.
140pub fn encode_packed(values: &[AbiValue]) -> Vec<u8> {
141    let mut buf = Vec::new();
142    for v in values {
143        match v {
144            AbiValue::Uint256(val) => {
145                // Strip leading zeros for packed
146                let start = val.iter().position(|b| *b != 0).unwrap_or(31);
147                buf.extend_from_slice(&val[start..]);
148            }
149            AbiValue::Int256(val) => buf.extend_from_slice(val),
150            AbiValue::Address(addr) => buf.extend_from_slice(addr),
151            AbiValue::Bool(b) => buf.push(if *b { 1 } else { 0 }),
152            AbiValue::FixedBytes(data) => buf.extend_from_slice(data),
153            AbiValue::Bytes(data) => buf.extend_from_slice(data),
154            AbiValue::String(s) => buf.extend_from_slice(s.as_bytes()),
155            AbiValue::Array(items) => {
156                for item in items {
157                    buf.extend_from_slice(&encode_packed(std::slice::from_ref(item)));
158                }
159            }
160            AbiValue::Tuple(items) => {
161                buf.extend_from_slice(&encode_packed(items));
162            }
163        }
164    }
165    buf
166}
167
168/// Compute the keccak256 of the ABI-encoded values.
169pub fn encode_and_hash(values: &[AbiValue]) -> [u8; 32] {
170    keccak256(&encode(values))
171}
172
173/// Compute the keccak256 of the packed ABI-encoded values.
174///
175/// Commonly used for `keccak256(abi.encodePacked(...))` in Solidity.
176pub fn encode_packed_and_hash(values: &[AbiValue]) -> [u8; 32] {
177    keccak256(&encode_packed(values))
178}
179
180// ─── Function Call ─────────────────────────────────────────────────
181
182/// An Ethereum contract function for ABI-encoding calls and decoding results.
183///
184/// # Example
185/// ```no_run
186/// use chains_sdk::ethereum::abi::{Function, AbiValue};
187///
188/// let transfer = Function::new("transfer(address,uint256)");
189/// assert_eq!(hex::encode(transfer.selector()), "a9059cbb");
190///
191/// let calldata = transfer.encode(&[
192///     AbiValue::Address([0xBB; 20]),
193///     AbiValue::from_u128(1_000_000_000_000_000_000), // 1 token (18 decimals)
194/// ]);
195/// ```
196pub struct Function {
197    /// The full function signature (e.g., `"transfer(address,uint256)"`).
198    signature: String,
199    /// The 4-byte function selector.
200    selector_bytes: [u8; 4],
201}
202
203impl Function {
204    /// Create a new function from its Solidity signature.
205    ///
206    /// The signature must follow the canonical format: `name(type1,type2,...)`
207    #[must_use]
208    pub fn new(signature: &str) -> Self {
209        let hash = keccak256(signature.as_bytes());
210        let mut selector = [0u8; 4];
211        selector.copy_from_slice(&hash[..4]);
212        Self {
213            signature: signature.to_string(),
214            selector_bytes: selector,
215        }
216    }
217
218    /// Return the 4-byte function selector.
219    #[must_use]
220    pub fn selector(&self) -> [u8; 4] {
221        self.selector_bytes
222    }
223
224    /// Return the function signature string.
225    #[must_use]
226    pub fn signature(&self) -> &str {
227        &self.signature
228    }
229
230    /// Encode a function call with the given arguments.
231    ///
232    /// Returns `selector || abi.encode(args...)`.
233    #[must_use]
234    pub fn encode(&self, args: &[AbiValue]) -> Vec<u8> {
235        let mut calldata = Vec::with_capacity(4 + args.len() * 32);
236        calldata.extend_from_slice(&self.selector_bytes);
237        calldata.extend_from_slice(&encode(args));
238        calldata
239    }
240}
241
242/// Compute the 4-byte function selector from a Solidity signature.
243///
244/// `keccak256("transfer(address,uint256)")[..4]`
245#[must_use]
246pub fn function_selector(signature: &str) -> [u8; 4] {
247    Function::new(signature).selector()
248}
249
250/// Compute the 32-byte event topic from a Solidity event signature.
251///
252/// `keccak256("Transfer(address,address,uint256)")`
253#[must_use]
254pub fn event_topic(signature: &str) -> [u8; 32] {
255    keccak256(signature.as_bytes())
256}
257
258// ─── ABI Decoding ──────────────────────────────────────────────────
259
260/// Decode a single `uint256` from 32 bytes.
261pub fn decode_uint256(data: &[u8]) -> Result<[u8; 32], SignerError> {
262    if data.len() < 32 {
263        return Err(SignerError::ParseError(
264            "ABI: need 32 bytes for uint256".into(),
265        ));
266    }
267    let mut buf = [0u8; 32];
268    buf.copy_from_slice(&data[..32]);
269    Ok(buf)
270}
271
272/// Decode a `uint256` as `u64` (fails if value > u64::MAX).
273pub fn decode_uint256_as_u64(data: &[u8]) -> Result<u64, SignerError> {
274    let raw = decode_uint256(data)?;
275    // Check that the first 24 bytes are zero
276    if raw[..24].iter().any(|b| *b != 0) {
277        return Err(SignerError::ParseError(
278            "ABI: uint256 overflow for u64".into(),
279        ));
280    }
281    let mut buf = [0u8; 8];
282    buf.copy_from_slice(&raw[24..32]);
283    Ok(u64::from_be_bytes(buf))
284}
285
286/// Decode an `address` from 32 padded bytes.
287pub fn decode_address(data: &[u8]) -> Result<[u8; 20], SignerError> {
288    if data.len() < 32 {
289        return Err(SignerError::ParseError(
290            "ABI: need 32 bytes for address".into(),
291        ));
292    }
293    if data[..12].iter().any(|b| *b != 0) {
294        return Err(SignerError::ParseError(
295            "ABI: non-canonical address padding".into(),
296        ));
297    }
298    let mut addr = [0u8; 20];
299    addr.copy_from_slice(&data[12..32]);
300    Ok(addr)
301}
302
303/// Decode a `bool` from 32 padded bytes.
304pub fn decode_bool(data: &[u8]) -> Result<bool, SignerError> {
305    if data.len() < 32 {
306        return Err(SignerError::ParseError(
307            "ABI: need 32 bytes for bool".into(),
308        ));
309    }
310    if data[..31].iter().any(|b| *b != 0) {
311        return Err(SignerError::ParseError(
312            "ABI: non-canonical bool padding".into(),
313        ));
314    }
315    match data[31] {
316        0 => Ok(false),
317        1 => Ok(true),
318        _ => Err(SignerError::ParseError(
319            "ABI: non-canonical bool value".into(),
320        )),
321    }
322}
323
324/// Decode dynamic `bytes` from ABI-encoded data at a given offset.
325///
326/// Reads the offset pointer, then the length-prefixed data.
327pub fn decode_bytes(data: &[u8], param_offset: usize) -> Result<Vec<u8>, SignerError> {
328    if param_offset > data.len() {
329        return Err(SignerError::ParseError(
330            "ABI: parameter offset out of range".into(),
331        ));
332    }
333    if param_offset % 32 != 0 {
334        return Err(SignerError::ParseError(
335            "ABI: parameter offset must be 32-byte aligned".into(),
336        ));
337    }
338
339    // Read the offset (big-endian u64 in 32 bytes)
340    let offset_u64 = decode_uint256_as_u64(&data[param_offset..])?;
341    let offset = usize::try_from(offset_u64)
342        .map_err(|_| SignerError::ParseError("ABI: bytes offset exceeds usize".into()))?;
343    if offset % 32 != 0 {
344        return Err(SignerError::ParseError(
345            "ABI: bytes offset must be 32-byte aligned".into(),
346        ));
347    }
348
349    // At `offset`: length (32 bytes) + data
350    let start = offset
351        .checked_add(32)
352        .ok_or_else(|| SignerError::ParseError("ABI: bytes offset overflow".into()))?;
353    if start > data.len() {
354        return Err(SignerError::ParseError(
355            "ABI: bytes offset out of range".into(),
356        ));
357    }
358
359    let len_u64 = decode_uint256_as_u64(&data[offset..])?;
360    let len = usize::try_from(len_u64)
361        .map_err(|_| SignerError::ParseError("ABI: bytes length exceeds usize".into()))?;
362    let end = start
363        .checked_add(len)
364        .ok_or_else(|| SignerError::ParseError("ABI: bytes length overflow".into()))?;
365    if end > data.len() {
366        return Err(SignerError::ParseError("ABI: bytes data truncated".into()));
367    }
368    Ok(data[start..end].to_vec())
369}
370
371/// Decode a dynamic `string` from ABI-encoded data at a given offset.
372pub fn decode_string(data: &[u8], param_offset: usize) -> Result<String, SignerError> {
373    let bytes = decode_bytes(data, param_offset)?;
374    String::from_utf8(bytes)
375        .map_err(|e| SignerError::ParseError(format!("ABI: invalid UTF-8: {e}")))
376}
377
378// ─── Contract Deployment ───────────────────────────────────────────
379
380/// Build contract deployment calldata: `bytecode || abi.encode(constructor_args)`.
381///
382/// # Arguments
383/// - `bytecode` — The compiled contract bytecode
384/// - `constructor_args` — Constructor arguments (empty if none)
385///
386/// Use this as the `data` field in a transaction with `to: None`.
387#[must_use]
388pub fn encode_constructor(bytecode: &[u8], constructor_args: &[AbiValue]) -> Vec<u8> {
389    let mut data = bytecode.to_vec();
390    if !constructor_args.is_empty() {
391        data.extend_from_slice(&encode(constructor_args));
392    }
393    data
394}
395
396/// Build and sign a contract deployment transaction (EIP-1559).
397#[allow(clippy::too_many_arguments)]
398pub fn deploy_contract(
399    signer: &super::EthereumSigner,
400    bytecode: &[u8],
401    constructor_args: &[AbiValue],
402    chain_id: u64,
403    nonce: u64,
404    max_priority_fee_per_gas: u128,
405    max_fee_per_gas: u128,
406    gas_limit: u64,
407) -> Result<super::transaction::SignedTransaction, SignerError> {
408    let tx = super::transaction::EIP1559Transaction {
409        chain_id,
410        nonce,
411        max_priority_fee_per_gas,
412        max_fee_per_gas,
413        gas_limit,
414        to: None, // contract creation
415        value: 0,
416        data: encode_constructor(bytecode, constructor_args),
417        access_list: vec![],
418    };
419    tx.sign(signer)
420}
421
422// ─── Contract Call Builder ─────────────────────────────────────────
423
424/// A builder for encoding and signing contract calls.
425///
426/// # Example
427/// ```no_run
428/// use chains_sdk::ethereum::abi::{ContractCall, AbiValue};
429///
430/// let call = ContractCall::new([0xAA; 20], "transfer(address,uint256)")
431///     .args(&[AbiValue::Address([0xBB; 20]), AbiValue::from_u128(1_000_000)])
432///     .value(0);
433///
434/// // For eth_call (read-only):
435/// let calldata = call.calldata();
436///
437/// // For eth_sendTransaction (state-changing):
438/// // let signed = call.sign(&signer, chain_id, nonce, fees...)?;
439/// ```
440pub struct ContractCall {
441    /// Target contract address.
442    contract: [u8; 20],
443    /// Function being called.
444    function: Function,
445    /// Encoded arguments.
446    args: Vec<AbiValue>,
447    /// ETH value to send (in wei).
448    value_wei: u128,
449}
450
451impl ContractCall {
452    /// Create a new contract call.
453    #[must_use]
454    pub fn new(contract: [u8; 20], function_signature: &str) -> Self {
455        Self {
456            contract,
457            function: Function::new(function_signature),
458            args: Vec::new(),
459            value_wei: 0,
460        }
461    }
462
463    /// Set the function arguments.
464    #[must_use]
465    pub fn args(mut self, args: &[AbiValue]) -> Self {
466        self.args = args.to_vec();
467        self
468    }
469
470    /// Set the ETH value to send with this call.
471    #[must_use]
472    pub fn value(mut self, value_wei: u128) -> Self {
473        self.value_wei = value_wei;
474        self
475    }
476
477    /// Get the encoded calldata (selector + encoded args).
478    ///
479    /// Use this for `eth_call` (read-only queries).
480    #[must_use]
481    pub fn calldata(&self) -> Vec<u8> {
482        self.function.encode(&self.args)
483    }
484
485    /// Build and sign an EIP-1559 transaction for this contract call.
486    pub fn sign(
487        &self,
488        signer: &super::EthereumSigner,
489        chain_id: u64,
490        nonce: u64,
491        max_priority_fee_per_gas: u128,
492        max_fee_per_gas: u128,
493        gas_limit: u64,
494    ) -> Result<super::transaction::SignedTransaction, SignerError> {
495        let tx = super::transaction::EIP1559Transaction {
496            chain_id,
497            nonce,
498            max_priority_fee_per_gas,
499            max_fee_per_gas,
500            gas_limit,
501            to: Some(self.contract),
502            value: self.value_wei,
503            data: self.calldata(),
504            access_list: vec![],
505        };
506        tx.sign(signer)
507    }
508}
509
510// ─── Internal Helpers ──────────────────────────────────────────────
511
512fn keccak256(data: &[u8]) -> [u8; 32] {
513    super::keccak256(data)
514}
515
516fn encode_dynamic_bytes(data: &[u8]) -> Vec<u8> {
517    let mut buf = Vec::new();
518    // Length (32 bytes, big-endian)
519    let mut len = [0u8; 32];
520    len[24..].copy_from_slice(&(data.len() as u64).to_be_bytes());
521    buf.extend_from_slice(&len);
522    // Data (padded to 32-byte boundary)
523    buf.extend_from_slice(data);
524    let padding = (32 - (data.len() % 32)) % 32;
525    buf.extend_from_slice(&vec![0u8; padding]);
526    buf
527}
528
529fn encode_tuple(values: &[AbiValue]) -> Vec<u8> {
530    let head_size = values.len() * 32;
531    let mut heads = Vec::with_capacity(head_size);
532    let mut tails = Vec::new();
533
534    for v in values {
535        if v.is_dynamic() {
536            // Head = offset to tail data
537            let offset = head_size + tails.len();
538            let mut offset_bytes = [0u8; 32];
539            offset_bytes[24..].copy_from_slice(&(offset as u64).to_be_bytes());
540            heads.extend_from_slice(&offset_bytes);
541            tails.extend_from_slice(&v.encode_tail());
542        } else {
543            heads.extend_from_slice(&v.encode_head());
544        }
545    }
546
547    let mut result = Vec::with_capacity(heads.len() + tails.len());
548    result.extend_from_slice(&heads);
549    result.extend_from_slice(&tails);
550    result
551}
552
553// ─── Tests ─────────────────────────────────────────────────────────
554
555#[cfg(test)]
556#[allow(clippy::unwrap_used, clippy::expect_used)]
557mod tests {
558    use super::*;
559    use crate::traits::KeyPair;
560
561    // ─── Function Selector Tests ───────────────────────────────────
562
563    #[test]
564    fn test_selector_transfer() {
565        // transfer(address,uint256) → 0xa9059cbb
566        let sel = function_selector("transfer(address,uint256)");
567        assert_eq!(hex::encode(sel), "a9059cbb");
568    }
569
570    #[test]
571    fn test_selector_approve() {
572        // approve(address,uint256) → 0x095ea7b3
573        let sel = function_selector("approve(address,uint256)");
574        assert_eq!(hex::encode(sel), "095ea7b3");
575    }
576
577    #[test]
578    fn test_selector_balance_of() {
579        // balanceOf(address) → 0x70a08231
580        let sel = function_selector("balanceOf(address)");
581        assert_eq!(hex::encode(sel), "70a08231");
582    }
583
584    #[test]
585    fn test_selector_total_supply() {
586        // totalSupply() → 0x18160ddd
587        let sel = function_selector("totalSupply()");
588        assert_eq!(hex::encode(sel), "18160ddd");
589    }
590
591    #[test]
592    fn test_event_topic_transfer() {
593        // Transfer(address,address,uint256) → known topic
594        let topic = event_topic("Transfer(address,address,uint256)");
595        assert_eq!(
596            hex::encode(topic),
597            "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
598        );
599    }
600
601    #[test]
602    fn test_event_topic_approval() {
603        let topic = event_topic("Approval(address,address,uint256)");
604        assert_eq!(
605            hex::encode(topic),
606            "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
607        );
608    }
609
610    // ─── ABI Encoding Tests ────────────────────────────────────────
611
612    #[test]
613    fn test_encode_uint256() {
614        let val = AbiValue::from_u64(42);
615        let encoded = encode(&[val]);
616        assert_eq!(encoded.len(), 32);
617        assert_eq!(encoded[31], 42);
618        assert!(encoded[..31].iter().all(|b| *b == 0));
619    }
620
621    #[test]
622    fn test_encode_address() {
623        let addr = [0xAA; 20];
624        let encoded = encode(&[AbiValue::Address(addr)]);
625        assert_eq!(encoded.len(), 32);
626        assert!(encoded[..12].iter().all(|b| *b == 0)); // left-padded
627        assert_eq!(&encoded[12..], &[0xAA; 20]);
628    }
629
630    #[test]
631    fn test_encode_bool() {
632        let encoded_true = encode(&[AbiValue::Bool(true)]);
633        assert_eq!(encoded_true[31], 1);
634        let encoded_false = encode(&[AbiValue::Bool(false)]);
635        assert_eq!(encoded_false[31], 0);
636    }
637
638    #[test]
639    fn test_encode_dynamic_bytes() {
640        let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
641        let encoded = encode(&[AbiValue::Bytes(data.clone())]);
642        // Head: offset (32 bytes) → 0x20 = 32
643        assert_eq!(encoded[31], 32);
644        // Tail: length (32 bytes) + padded data (32 bytes)
645        assert_eq!(encoded[63], 4); // length = 4
646        assert_eq!(&encoded[64..68], &data);
647    }
648
649    #[test]
650    fn test_encode_string() {
651        let s = "hello";
652        let encoded = encode(&[AbiValue::String(s.to_string())]);
653        assert_eq!(encoded[31], 32); // offset
654        assert_eq!(encoded[63], 5); // length
655        assert_eq!(&encoded[64..69], b"hello");
656    }
657
658    #[test]
659    fn test_encode_multiple_static() {
660        // abi.encode(address, uint256)
661        let encoded = encode(&[AbiValue::Address([0xBB; 20]), AbiValue::from_u64(100)]);
662        assert_eq!(encoded.len(), 64); // 2 × 32
663        assert_eq!(&encoded[12..32], &[0xBB; 20]);
664        assert_eq!(encoded[63], 100);
665    }
666
667    #[test]
668    fn test_function_encode_transfer() {
669        let transfer = Function::new("transfer(address,uint256)");
670        let calldata = transfer.encode(&[AbiValue::Address([0xCC; 20]), AbiValue::from_u64(1000)]);
671        assert_eq!(&calldata[..4], &hex::decode("a9059cbb").unwrap());
672        assert_eq!(calldata.len(), 4 + 64);
673    }
674
675    // ─── encode_packed Tests ───────────────────────────────────────
676
677    #[test]
678    fn test_encode_packed_address_uint() {
679        let packed = encode_packed(&[AbiValue::Address([0xAA; 20]), AbiValue::from_u64(1)]);
680        // address = 20 bytes + uint = 1 byte (stripped)
681        assert_eq!(&packed[..20], &[0xAA; 20]);
682        assert_eq!(packed[20], 1);
683    }
684
685    #[test]
686    fn test_encode_packed_and_hash() {
687        let hash = encode_packed_and_hash(&[
688            AbiValue::String("hello".to_string()),
689            AbiValue::String("world".to_string()),
690        ]);
691        // keccak256("helloworld")
692        let expected = keccak256(b"helloworld");
693        assert_eq!(hash, expected);
694    }
695
696    // ─── ABI Decoding Tests ────────────────────────────────────────
697
698    #[test]
699    fn test_decode_uint256_roundtrip() {
700        let val = AbiValue::from_u64(12345);
701        let encoded = encode(&[val]);
702        let decoded = decode_uint256_as_u64(&encoded).unwrap();
703        assert_eq!(decoded, 12345);
704    }
705
706    #[test]
707    fn test_decode_address_roundtrip() {
708        let addr = [0xDD; 20];
709        let encoded = encode(&[AbiValue::Address(addr)]);
710        let decoded = decode_address(&encoded).unwrap();
711        assert_eq!(decoded, addr);
712    }
713
714    #[test]
715    fn test_decode_bool_roundtrip() {
716        let encoded = encode(&[AbiValue::Bool(true)]);
717        assert!(decode_bool(&encoded).unwrap());
718        let encoded = encode(&[AbiValue::Bool(false)]);
719        assert!(!decode_bool(&encoded).unwrap());
720    }
721
722    #[test]
723    fn test_decode_address_rejects_non_canonical_padding() {
724        let mut encoded = encode(&[AbiValue::Address([0x11; 20])]);
725        encoded[0] = 0x01; // upper 12-byte padding must be zero
726        assert!(decode_address(&encoded).is_err());
727    }
728
729    #[test]
730    fn test_decode_bool_rejects_non_canonical_value() {
731        let mut encoded = [0u8; 32];
732        encoded[31] = 2; // only 0 or 1 are canonical
733        assert!(decode_bool(&encoded).is_err());
734    }
735
736    #[test]
737    fn test_decode_bool_rejects_non_canonical_padding() {
738        let mut encoded = [0u8; 32];
739        encoded[0] = 1; // padding bytes must be zero
740        encoded[31] = 1;
741        assert!(decode_bool(&encoded).is_err());
742    }
743
744    #[test]
745    fn test_decode_bytes_roundtrip() {
746        let data = vec![0xCA, 0xFE, 0xBA, 0xBE];
747        let encoded = encode(&[AbiValue::Bytes(data.clone())]);
748        let decoded = decode_bytes(&encoded, 0).unwrap();
749        assert_eq!(decoded, data);
750    }
751
752    #[test]
753    fn test_decode_string_roundtrip() {
754        let s = "Hello, Ethereum!";
755        let encoded = encode(&[AbiValue::String(s.to_string())]);
756        let decoded = decode_string(&encoded, 0).unwrap();
757        assert_eq!(decoded, s);
758    }
759
760    #[test]
761    fn test_decode_bytes_param_offset_out_of_range() {
762        let encoded = encode(&[AbiValue::Bytes(vec![0xAA, 0xBB])]);
763        assert!(decode_bytes(&encoded, encoded.len() + 1).is_err());
764    }
765
766    #[test]
767    fn test_decode_bytes_rejects_unaligned_param_offset() {
768        let encoded = encode(&[AbiValue::Bytes(vec![0xAA, 0xBB])]);
769        assert!(decode_bytes(&encoded, 1).is_err());
770    }
771
772    #[test]
773    fn test_decode_bytes_rejects_unaligned_dynamic_offset() {
774        let mut encoded = encode(&[AbiValue::Bytes(vec![0xAA, 0xBB])]);
775        // Corrupt dynamic offset from 0x20 to 0x21
776        encoded[31] = 0x21;
777        assert!(decode_bytes(&encoded, 0).is_err());
778    }
779
780    // ─── Contract Deploy Tests ─────────────────────────────────────
781
782    #[test]
783    fn test_encode_constructor_no_args() {
784        let bytecode = vec![0x60, 0x00, 0x60, 0x00]; // minimal
785        let data = encode_constructor(&bytecode, &[]);
786        assert_eq!(data, bytecode);
787    }
788
789    #[test]
790    fn test_encode_constructor_with_args() {
791        let bytecode = vec![0x60, 0x00];
792        let data = encode_constructor(&bytecode, &[AbiValue::from_u64(42)]);
793        assert_eq!(&data[..2], &bytecode);
794        assert_eq!(data.len(), 2 + 32);
795        assert_eq!(data[33], 42);
796    }
797
798    #[test]
799    fn test_deploy_contract_signs() {
800        let signer = super::super::EthereumSigner::generate().unwrap();
801        let signed = deploy_contract(
802            &signer,
803            &[0x60, 0x00],
804            &[],
805            1,
806            0,
807            2_000_000_000,
808            100_000_000_000,
809            1_000_000,
810        )
811        .unwrap();
812        assert_eq!(signed.raw_tx()[0], 0x02); // EIP-1559
813    }
814
815    // ─── Contract Call Tests ───────────────────────────────────────
816
817    #[test]
818    fn test_contract_call_calldata() {
819        let call = ContractCall::new([0xAA; 20], "transfer(address,uint256)")
820            .args(&[AbiValue::Address([0xBB; 20]), AbiValue::from_u64(1000)]);
821        let cd = call.calldata();
822        assert_eq!(&cd[..4], &hex::decode("a9059cbb").unwrap());
823    }
824
825    #[test]
826    fn test_contract_call_sign() {
827        let signer = super::super::EthereumSigner::generate().unwrap();
828        let call = ContractCall::new([0xAA; 20], "transfer(address,uint256)")
829            .args(&[AbiValue::Address([0xBB; 20]), AbiValue::from_u64(1000)])
830            .value(0);
831        let signed = call
832            .sign(&signer, 1, 0, 2_000_000_000, 100_000_000_000, 100_000)
833            .unwrap();
834        assert_eq!(signed.raw_tx()[0], 0x02);
835    }
836
837    // ─── Fixed Bytes Tests ─────────────────────────────────────────
838
839    #[test]
840    fn test_encode_fixed_bytes() {
841        let val = AbiValue::FixedBytes(vec![0xAA, 0xBB, 0xCC, 0xDD]);
842        let encoded = encode(&[val]);
843        assert_eq!(encoded.len(), 32);
844        assert_eq!(&encoded[..4], &[0xAA, 0xBB, 0xCC, 0xDD]);
845        assert!(encoded[4..].iter().all(|b| *b == 0)); // right-padded
846    }
847
848    // ─── Array Tests ───────────────────────────────────────────────
849
850    #[test]
851    fn test_encode_array() {
852        let arr = AbiValue::Array(vec![
853            AbiValue::from_u64(1),
854            AbiValue::from_u64(2),
855            AbiValue::from_u64(3),
856        ]);
857        let encoded = encode(&[arr]);
858        // Head: offset (32 bytes)
859        // Tail: length (32 bytes) + 3 × 32 bytes
860        assert_eq!(encoded.len(), 32 + 32 + 3 * 32); // = 192
861    }
862}