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