Skip to main content

movement_sdk/transaction/
input.rs

1//! Type-safe entry function payload builders.
2//!
3//! This module provides ergonomic builders for constructing entry function
4//! payloads with automatic BCS encoding of arguments.
5//!
6//! # Overview
7//!
8//! `InputEntryFunctionData` provides a builder pattern that:
9//! - Accepts Rust types directly (no manual BCS encoding)
10//! - Validates function IDs at construction
11//! - Supports all Move types
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! use movement_sdk::transaction::InputEntryFunctionData;
17//!
18//! // Simple transfer
19//! let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
20//!     .arg(recipient_address)
21//!     .arg(1_000_000u64)
22//!     .build()?;
23//!
24//! // Generic function with type args
25//! let payload = InputEntryFunctionData::new("0x1::coin::transfer")
26//!     .type_arg("0x1::aptos_coin::AptosCoin")
27//!     .arg(recipient_address)
28//!     .arg(amount)
29//!     .build()?;
30//! ```
31
32use crate::error::{MovementError, MovementResult};
33use crate::transaction::{EntryFunction, TransactionPayload};
34use crate::types::{AccountAddress, EntryFunctionId, MoveModuleId, TypeTag};
35use serde::Serialize;
36
37/// A type-safe builder for entry function payloads.
38///
39/// This builder provides an ergonomic way to construct entry function calls
40/// with automatic BCS encoding of arguments.
41///
42/// # Example
43///
44/// ```rust,ignore
45/// use movement_sdk::transaction::InputEntryFunctionData;
46/// use movement_sdk::types::AccountAddress;
47///
48/// let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
49///     .arg(AccountAddress::from_hex("0x123").unwrap())
50///     .arg(1_000_000u64)  // 0.01 APT in octas
51///     .build()?;
52/// ```
53#[allow(dead_code)] // Public API struct - fields used via builder pattern
54#[derive(Debug, Clone)]
55pub struct InputEntryFunctionData {
56    module: MoveModuleId,
57    function: String,
58    type_args: Vec<TypeTag>,
59    args: Vec<Vec<u8>>,
60}
61
62impl InputEntryFunctionData {
63    /// Creates a new entry function data builder.
64    ///
65    /// # Arguments
66    ///
67    /// * `function_id` - The full function identifier (e.g., "`0x1::coin::transfer`")
68    ///
69    /// # Example
70    ///
71    /// ```rust,ignore
72    /// let builder = InputEntryFunctionData::new("0x1::aptos_account::transfer");
73    /// ```
74    #[allow(clippy::new_ret_no_self)] // Returns builder pattern intentionally
75    pub fn new(function_id: &str) -> InputEntryFunctionDataBuilder {
76        InputEntryFunctionDataBuilder::new(function_id)
77    }
78
79    /// Creates a builder from module and function name.
80    ///
81    /// # Arguments
82    ///
83    /// * `module` - The module ID
84    /// * `function` - The function name
85    pub fn from_parts(
86        module: MoveModuleId,
87        function: impl Into<String>,
88    ) -> InputEntryFunctionDataBuilder {
89        InputEntryFunctionDataBuilder {
90            module: Ok(module),
91            function: function.into(),
92            type_args: Vec::new(),
93            args: Vec::new(),
94            errors: Vec::new(),
95        }
96    }
97
98    /// Builds an APT transfer payload.
99    ///
100    /// # Arguments
101    ///
102    /// * `recipient` - The recipient address
103    /// * `amount` - Amount in octas (1 APT = 10^8 octas)
104    ///
105    /// # Example
106    ///
107    /// ```rust,ignore
108    /// let payload = InputEntryFunctionData::transfer_apt(recipient, 1_000_000)?;
109    /// ```
110    ///
111    /// # Errors
112    ///
113    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
114    pub fn transfer_apt(
115        recipient: AccountAddress,
116        amount: u64,
117    ) -> MovementResult<TransactionPayload> {
118        InputEntryFunctionData::new("0x1::aptos_account::transfer")
119            .arg(recipient)
120            .arg(amount)
121            .build()
122    }
123
124    /// Builds a coin transfer payload for any coin type.
125    ///
126    /// # Arguments
127    ///
128    /// * `coin_type` - The coin type (e.g., "`0x1::aptos_coin::AptosCoin`")
129    /// * `recipient` - The recipient address
130    /// * `amount` - Amount in the coin's smallest unit
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the function ID is invalid, the coin type is invalid, or if BCS encoding of arguments fails.
135    pub fn transfer_coin(
136        coin_type: &str,
137        recipient: AccountAddress,
138        amount: u64,
139    ) -> MovementResult<TransactionPayload> {
140        InputEntryFunctionData::new("0x1::coin::transfer")
141            .type_arg(coin_type)
142            .arg(recipient)
143            .arg(amount)
144            .build()
145    }
146
147    /// Builds an account creation payload.
148    ///
149    /// # Arguments
150    ///
151    /// * `auth_key` - The authentication key (32 bytes)
152    ///
153    /// # Errors
154    ///
155    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
156    pub fn create_account(auth_key: AccountAddress) -> MovementResult<TransactionPayload> {
157        InputEntryFunctionData::new("0x1::aptos_account::create_account")
158            .arg(auth_key)
159            .build()
160    }
161
162    /// Builds a payload to rotate an account's authentication key.
163    ///
164    /// # Arguments
165    ///
166    /// * Various rotation parameters
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
171    pub fn rotate_authentication_key(
172        from_scheme: u8,
173        from_public_key_bytes: Vec<u8>,
174        to_scheme: u8,
175        to_public_key_bytes: Vec<u8>,
176        cap_rotate_key: Vec<u8>,
177        cap_update_table: Vec<u8>,
178    ) -> MovementResult<TransactionPayload> {
179        InputEntryFunctionData::new("0x1::account::rotate_authentication_key")
180            .arg(from_scheme)
181            .arg(from_public_key_bytes)
182            .arg(to_scheme)
183            .arg(to_public_key_bytes)
184            .arg(cap_rotate_key)
185            .arg(cap_update_table)
186            .build()
187    }
188
189    /// Builds a payload to register a coin store.
190    ///
191    /// # Arguments
192    ///
193    /// * `coin_type` - The coin type to register
194    ///
195    /// # Errors
196    ///
197    /// Returns an error if the function ID is invalid, the coin type is invalid, or if building the payload fails.
198    pub fn register_coin(coin_type: &str) -> MovementResult<TransactionPayload> {
199        InputEntryFunctionData::new("0x1::managed_coin::register")
200            .type_arg(coin_type)
201            .build()
202    }
203
204    /// Builds a payload to publish a module.
205    ///
206    /// # Arguments
207    ///
208    /// * `metadata_serialized` - Serialized module metadata
209    /// * `code` - Vector of module bytecode
210    ///
211    /// # Errors
212    ///
213    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
214    pub fn publish_package(
215        metadata_serialized: Vec<u8>,
216        code: Vec<Vec<u8>>,
217    ) -> MovementResult<TransactionPayload> {
218        InputEntryFunctionData::new("0x1::code::publish_package_txn")
219            .arg(metadata_serialized)
220            .arg(code)
221            .build()
222    }
223}
224
225/// Builder for `InputEntryFunctionData`.
226#[derive(Debug, Clone)]
227pub struct InputEntryFunctionDataBuilder {
228    module: Result<MoveModuleId, String>,
229    function: String,
230    type_args: Vec<TypeTag>,
231    args: Vec<Vec<u8>>,
232    errors: Vec<String>,
233}
234
235impl InputEntryFunctionDataBuilder {
236    /// Creates a new builder from a function ID string.
237    #[must_use]
238    fn new(function_id: &str) -> Self {
239        match EntryFunctionId::from_str_strict(function_id) {
240            Ok(func_id) => Self {
241                module: Ok(func_id.module),
242                function: func_id.name.as_str().to_string(),
243                type_args: Vec::new(),
244                args: Vec::new(),
245                errors: Vec::new(),
246            },
247            Err(e) => Self {
248                module: Err(format!("Invalid function ID '{function_id}': {e}")),
249                function: String::new(),
250                type_args: Vec::new(),
251                args: Vec::new(),
252                errors: Vec::new(),
253            },
254        }
255    }
256
257    /// Adds a type argument.
258    ///
259    /// # Arguments
260    ///
261    /// * `type_arg` - A type tag string (e.g., "`0x1::aptos_coin::AptosCoin`")
262    ///
263    /// # Example
264    ///
265    /// ```rust,ignore
266    /// let builder = InputEntryFunctionData::new("0x1::coin::transfer")
267    ///     .type_arg("0x1::aptos_coin::AptosCoin");
268    /// ```
269    #[must_use]
270    pub fn type_arg(mut self, type_arg: &str) -> Self {
271        match TypeTag::from_str_strict(type_arg) {
272            Ok(tag) => self.type_args.push(tag),
273            Err(e) => self
274                .errors
275                .push(format!("Invalid type argument '{type_arg}': {e}")),
276        }
277        self
278    }
279
280    /// Adds a type argument from a `TypeTag`.
281    #[must_use]
282    pub fn type_arg_typed(mut self, type_arg: TypeTag) -> Self {
283        self.type_args.push(type_arg);
284        self
285    }
286
287    /// Adds multiple type arguments.
288    #[must_use]
289    pub fn type_args(mut self, type_args: impl IntoIterator<Item = &'static str>) -> Self {
290        for type_arg in type_args {
291            self = self.type_arg(type_arg);
292        }
293        self
294    }
295
296    /// Adds multiple typed type arguments.
297    #[must_use]
298    pub fn type_args_typed(mut self, type_args: impl IntoIterator<Item = TypeTag>) -> Self {
299        self.type_args.extend(type_args);
300        self
301    }
302
303    /// Adds a BCS-encodable argument.
304    ///
305    /// Accepts any type that implements `Serialize` (BCS encoding).
306    ///
307    /// # Supported Types
308    ///
309    /// - Integers: `u8`, `u16`, `u32`, `u64`, `u128`
310    /// - Boolean: `bool`
311    /// - Strings: `String`, `&str`
312    /// - Addresses: `AccountAddress`
313    /// - Vectors: `Vec<T>` where T is serializable
314    /// - Bytes: `Vec<u8>`, `&[u8]`
315    ///
316    /// # Example
317    ///
318    /// ```rust,ignore
319    /// let builder = InputEntryFunctionData::new("0x1::my_module::my_function")
320    ///     .arg(42u64)
321    ///     .arg(true)
322    ///     .arg(AccountAddress::ONE)
323    ///     .arg("hello".to_string());
324    /// ```
325    #[must_use]
326    pub fn arg<T: Serialize>(mut self, value: T) -> Self {
327        match aptos_bcs::to_bytes(&value) {
328            Ok(bytes) => self.args.push(bytes),
329            Err(e) => self
330                .errors
331                .push(format!("Failed to serialize argument: {e}")),
332        }
333        self
334    }
335
336    /// Adds a raw BCS-encoded argument.
337    ///
338    /// Use this when you have pre-encoded bytes.
339    #[must_use]
340    pub fn arg_raw(mut self, bytes: Vec<u8>) -> Self {
341        self.args.push(bytes);
342        self
343    }
344
345    /// Adds multiple BCS-encodable arguments.
346    #[must_use]
347    pub fn args<T: Serialize>(mut self, values: impl IntoIterator<Item = T>) -> Self {
348        for value in values {
349            self = self.arg(value);
350        }
351        self
352    }
353
354    /// Builds the transaction payload.
355    ///
356    /// # Returns
357    ///
358    /// The constructed `TransactionPayload`, or an error if any
359    /// validation or serialization failed.
360    ///
361    /// # Errors
362    ///
363    /// Returns an error if the function ID is invalid, any type argument is invalid, or if any argument serialization failed.
364    pub fn build(self) -> MovementResult<TransactionPayload> {
365        // Check for module parsing error
366        let module = self.module.map_err(MovementError::Transaction)?;
367
368        // Check for any accumulated errors
369        if !self.errors.is_empty() {
370            return Err(MovementError::Transaction(self.errors.join("; ")));
371        }
372
373        Ok(TransactionPayload::EntryFunction(EntryFunction {
374            module,
375            function: self.function,
376            type_args: self.type_args,
377            args: self.args,
378        }))
379    }
380
381    /// Builds just the entry function (without wrapping in `TransactionPayload`).
382    ///
383    /// # Errors
384    ///
385    /// Returns an error if the function ID is invalid, any type argument is invalid, or if any argument serialization failed.
386    pub fn build_entry_function(self) -> MovementResult<EntryFunction> {
387        let module = self.module.map_err(MovementError::Transaction)?;
388
389        if !self.errors.is_empty() {
390            return Err(MovementError::Transaction(self.errors.join("; ")));
391        }
392
393        Ok(EntryFunction {
394            module,
395            function: self.function,
396            type_args: self.type_args,
397            args: self.args,
398        })
399    }
400}
401
402/// Trait for types that can be converted to entry function arguments.
403///
404/// This trait is automatically implemented for types that implement `Serialize`.
405pub trait IntoMoveArg {
406    /// Converts this value into BCS-encoded bytes.
407    ///
408    /// # Errors
409    ///
410    /// Returns an error if BCS serialization fails.
411    fn into_move_arg(self) -> MovementResult<Vec<u8>>;
412}
413
414impl<T: Serialize> IntoMoveArg for T {
415    fn into_move_arg(self) -> MovementResult<Vec<u8>> {
416        aptos_bcs::to_bytes(&self).map_err(MovementError::bcs)
417    }
418}
419
420/// Helper to create a vector argument for Move functions.
421///
422/// Move vectors are BCS-encoded with a length prefix followed by elements.
423///
424/// # Example
425///
426/// ```rust,ignore
427/// let addresses = move_vec(&[addr1, addr2, addr3])?;
428/// let amounts = move_vec(&[100u64, 200u64, 300u64])?;
429/// ```
430///
431/// # Errors
432///
433/// Returns `MovementError::Bcs` if BCS serialization of `items` fails.
434pub fn move_vec<T: Serialize>(items: &[T]) -> MovementResult<Vec<u8>> {
435    aptos_bcs::to_bytes(items).map_err(MovementError::bcs)
436}
437
438/// Helper to create a string argument for Move functions.
439///
440/// Move strings are UTF-8 encoded vectors of bytes.
441///
442/// # Example
443///
444/// ```rust,ignore
445/// let name = move_string("Alice");
446/// ```
447pub fn move_string(s: &str) -> String {
448    s.to_string()
449}
450
451/// Helper to create an `Option::Some` argument for Move.
452///
453/// # Example
454///
455/// ```rust,ignore
456/// let maybe_value = move_some(42u64)?;
457/// ```
458///
459/// # Errors
460///
461/// Returns `MovementError::Bcs` if BCS serialization of `value` fails.
462pub fn move_some<T: Serialize>(value: T) -> MovementResult<Vec<u8>> {
463    // BCS encodes Option as: 0x01 followed by the value bytes for Some
464    let mut bytes = vec![0x01];
465    bytes.extend(aptos_bcs::to_bytes(&value).map_err(MovementError::bcs)?);
466    Ok(bytes)
467}
468
469/// Helper to create an `Option::None` argument for Move.
470///
471/// # Example
472///
473/// ```rust,ignore
474/// let maybe_value: Vec<u8> = move_none();
475/// ```
476pub fn move_none() -> Vec<u8> {
477    // BCS encodes Option as: 0x00 for None
478    vec![0x00]
479}
480
481/// A u256 value for Move arguments.
482///
483/// Move's u256 is a 256-bit unsigned integer, represented as 32 bytes in little-endian.
484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
485pub struct MoveU256(pub [u8; 32]);
486
487impl MoveU256 {
488    /// Creates a `MoveU256` from a decimal string.
489    ///
490    /// # Errors
491    ///
492    /// Returns an error if the string cannot be parsed as a u256 value.
493    pub fn parse(s: &str) -> MovementResult<Self> {
494        // Parse as big integer and convert to little-endian bytes
495        let mut bytes = [0u8; 32];
496
497        // Simple parsing for small values
498        if let Ok(val) = s.parse::<u128>() {
499            bytes[..16].copy_from_slice(&val.to_le_bytes());
500            return Ok(Self(bytes));
501        }
502
503        Err(MovementError::Transaction(format!("Invalid u256: {s}")))
504    }
505
506    /// Creates a `MoveU256` from a u128.
507    pub fn from_u128(val: u128) -> Self {
508        let mut bytes = [0u8; 32];
509        bytes[..16].copy_from_slice(&val.to_le_bytes());
510        Self(bytes)
511    }
512
513    /// Creates a `MoveU256` from raw bytes (little-endian).
514    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
515        Self(bytes)
516    }
517}
518
519impl Serialize for MoveU256 {
520    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
521    where
522        S: serde::Serializer,
523    {
524        // BCS serializes u256 as 32 little-endian bytes (as a tuple of bytes, not a vec)
525        use serde::ser::SerializeTuple;
526        let mut tuple = serializer.serialize_tuple(32)?;
527        for byte in &self.0 {
528            tuple.serialize_element(byte)?;
529        }
530        tuple.end()
531    }
532}
533
534/// An i128 value for Move arguments.
535///
536/// Move's i128 is a 128-bit signed integer, represented as 16 bytes in little-endian
537/// using two's complement representation.
538#[derive(Debug, Clone, Copy, PartialEq, Eq)]
539pub struct MoveI128(pub i128);
540
541impl MoveI128 {
542    /// Creates a new `MoveI128` from an i128 value.
543    pub fn new(val: i128) -> Self {
544        Self(val)
545    }
546}
547
548impl Serialize for MoveI128 {
549    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
550    where
551        S: serde::Serializer,
552    {
553        // BCS serializes i128 as 16 little-endian bytes (two's complement)
554        use serde::ser::SerializeTuple;
555        let bytes = self.0.to_le_bytes();
556        let mut tuple = serializer.serialize_tuple(16)?;
557        for byte in &bytes {
558            tuple.serialize_element(byte)?;
559        }
560        tuple.end()
561    }
562}
563
564impl From<i128> for MoveI128 {
565    fn from(val: i128) -> Self {
566        Self(val)
567    }
568}
569
570/// An i256 value for Move arguments.
571///
572/// Move's i256 is a 256-bit signed integer, represented as 32 bytes in little-endian
573/// using two's complement representation.
574#[derive(Debug, Clone, Copy, PartialEq, Eq)]
575pub struct MoveI256(pub [u8; 32]);
576
577impl MoveI256 {
578    /// Creates a `MoveI256` from an i128 value.
579    pub fn from_i128(val: i128) -> Self {
580        let mut bytes = [0u8; 32];
581        let val_bytes = val.to_le_bytes();
582        bytes[..16].copy_from_slice(&val_bytes);
583        // Sign extend for negative values
584        if val < 0 {
585            bytes[16..].fill(0xFF);
586        }
587        Self(bytes)
588    }
589
590    /// Creates a `MoveI256` from raw bytes (little-endian, two's complement).
591    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
592        Self(bytes)
593    }
594}
595
596impl Serialize for MoveI256 {
597    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
598    where
599        S: serde::Serializer,
600    {
601        // BCS serializes i256 as 32 little-endian bytes (as a tuple of bytes, not a vec)
602        use serde::ser::SerializeTuple;
603        let mut tuple = serializer.serialize_tuple(32)?;
604        for byte in &self.0 {
605            tuple.serialize_element(byte)?;
606        }
607        tuple.end()
608    }
609}
610
611impl From<i128> for MoveI256 {
612    fn from(val: i128) -> Self {
613        Self::from_i128(val)
614    }
615}
616
617/// Common function IDs for convenience.
618pub mod functions {
619    /// APT transfer function.
620    pub const APT_TRANSFER: &str = "0x1::aptos_account::transfer";
621    /// Coin transfer function.
622    pub const COIN_TRANSFER: &str = "0x1::coin::transfer";
623    /// Account creation function.
624    pub const CREATE_ACCOUNT: &str = "0x1::aptos_account::create_account";
625    /// Register a coin store.
626    pub const REGISTER_COIN: &str = "0x1::managed_coin::register";
627    /// Publish a package.
628    pub const PUBLISH_PACKAGE: &str = "0x1::code::publish_package_txn";
629    /// Rotate authentication key.
630    pub const ROTATE_AUTH_KEY: &str = "0x1::account::rotate_authentication_key";
631}
632
633/// Common type tags for convenience.
634pub mod types {
635    /// APT coin type.
636    pub const APT_COIN: &str = "0x1::aptos_coin::AptosCoin";
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642
643    #[test]
644    fn test_simple_transfer() {
645        let recipient = AccountAddress::from_hex("0x123").unwrap();
646        let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
647            .arg(recipient)
648            .arg(1_000_000u64)
649            .build()
650            .unwrap();
651
652        match payload {
653            TransactionPayload::EntryFunction(ef) => {
654                assert_eq!(ef.function, "transfer");
655                assert_eq!(ef.module.name.as_str(), "aptos_account");
656                assert!(ef.type_args.is_empty());
657                assert_eq!(ef.args.len(), 2);
658            }
659            _ => panic!("Expected EntryFunction"),
660        }
661    }
662
663    #[test]
664    fn test_with_type_args() {
665        let payload = InputEntryFunctionData::new("0x1::coin::transfer")
666            .type_arg("0x1::aptos_coin::AptosCoin")
667            .arg(AccountAddress::ONE)
668            .arg(1000u64)
669            .build()
670            .unwrap();
671
672        match payload {
673            TransactionPayload::EntryFunction(ef) => {
674                assert_eq!(ef.function, "transfer");
675                assert_eq!(ef.type_args.len(), 1);
676            }
677            _ => panic!("Expected EntryFunction"),
678        }
679    }
680
681    #[test]
682    fn test_invalid_function_id() {
683        let result = InputEntryFunctionData::new("invalid").arg(42u64).build();
684
685        assert!(result.is_err());
686    }
687
688    #[test]
689    fn test_invalid_type_arg() {
690        let result = InputEntryFunctionData::new("0x1::coin::transfer")
691            .type_arg("not a type")
692            .arg(AccountAddress::ONE)
693            .arg(1000u64)
694            .build();
695
696        assert!(result.is_err());
697    }
698
699    #[test]
700    fn test_transfer_apt_helper() {
701        let recipient = AccountAddress::from_hex("0x456").unwrap();
702        let payload = InputEntryFunctionData::transfer_apt(recipient, 5_000_000).unwrap();
703
704        match payload {
705            TransactionPayload::EntryFunction(ef) => {
706                assert_eq!(ef.function, "transfer");
707                assert_eq!(ef.module.name.as_str(), "aptos_account");
708            }
709            _ => panic!("Expected EntryFunction"),
710        }
711    }
712
713    #[test]
714    fn test_transfer_coin_helper() {
715        let recipient = AccountAddress::from_hex("0x789").unwrap();
716        let payload =
717            InputEntryFunctionData::transfer_coin("0x1::aptos_coin::AptosCoin", recipient, 1000)
718                .unwrap();
719
720        match payload {
721            TransactionPayload::EntryFunction(ef) => {
722                assert_eq!(ef.function, "transfer");
723                assert_eq!(ef.module.name.as_str(), "coin");
724                assert_eq!(ef.type_args.len(), 1);
725            }
726            _ => panic!("Expected EntryFunction"),
727        }
728    }
729
730    #[test]
731    fn test_various_arg_types() {
732        let payload = InputEntryFunctionData::new("0x1::test::test_function")
733            .arg(42u8)
734            .arg(1000u64)
735            .arg(true)
736            .arg("hello".to_string())
737            .arg(vec![1u8, 2u8, 3u8])
738            .arg(AccountAddress::ONE)
739            .build()
740            .unwrap();
741
742        match payload {
743            TransactionPayload::EntryFunction(ef) => {
744                assert_eq!(ef.args.len(), 6);
745            }
746            _ => panic!("Expected EntryFunction"),
747        }
748    }
749
750    #[test]
751    fn test_move_u256() {
752        let val = MoveU256::from_u128(12345);
753        let bytes = aptos_bcs::to_bytes(&val).unwrap();
754        assert_eq!(bytes.len(), 32);
755    }
756
757    #[test]
758    fn test_move_some_none() {
759        let some_bytes = move_some(42u64).unwrap();
760        assert_eq!(some_bytes[0], 0x01);
761
762        let none_bytes = move_none();
763        assert_eq!(none_bytes, vec![0x00]);
764    }
765
766    #[test]
767    fn test_from_parts() {
768        let module = MoveModuleId::from_str_strict("0x1::coin").unwrap();
769        let payload = InputEntryFunctionData::from_parts(module, "transfer")
770            .type_arg("0x1::aptos_coin::AptosCoin")
771            .arg(AccountAddress::ONE)
772            .arg(1000u64)
773            .build()
774            .unwrap();
775
776        match payload {
777            TransactionPayload::EntryFunction(ef) => {
778                assert_eq!(ef.function, "transfer");
779                assert_eq!(ef.module.name.as_str(), "coin");
780            }
781            _ => panic!("Expected EntryFunction"),
782        }
783    }
784
785    #[test]
786    fn test_build_entry_function() {
787        let ef = InputEntryFunctionData::new("0x1::aptos_account::transfer")
788            .arg(AccountAddress::ONE)
789            .arg(1000u64)
790            .build_entry_function()
791            .unwrap();
792
793        assert_eq!(ef.function, "transfer");
794        assert_eq!(ef.args.len(), 2);
795    }
796
797    #[test]
798    fn test_function_constants() {
799        assert_eq!(functions::APT_TRANSFER, "0x1::aptos_account::transfer");
800        assert_eq!(functions::COIN_TRANSFER, "0x1::coin::transfer");
801    }
802
803    #[test]
804    fn test_move_u256_from_u128() {
805        let val = MoveU256::from_u128(123_456_789);
806        // First 16 bytes should contain the value in little-endian
807        let expected = 123_456_789_u128.to_le_bytes();
808        assert_eq!(&val.0[..16], &expected);
809        // Upper 16 bytes should be zero
810        assert_eq!(&val.0[16..], &[0u8; 16]);
811    }
812
813    #[test]
814    fn test_move_u256_from_le_bytes() {
815        let bytes = [0xab; 32];
816        let val = MoveU256::from_le_bytes(bytes);
817        assert_eq!(val.0, bytes);
818    }
819
820    #[test]
821    fn test_move_u256_parse() {
822        let val = MoveU256::parse("12345678901234567890").unwrap();
823        let expected = 12_345_678_901_234_567_890_u128;
824        let mut expected_bytes = [0u8; 32];
825        expected_bytes[..16].copy_from_slice(&expected.to_le_bytes());
826        assert_eq!(val.0, expected_bytes);
827    }
828
829    #[test]
830    fn test_move_u256_parse_invalid() {
831        // Value larger than u128 currently returns error
832        let result = MoveU256::parse("999999999999999999999999999999999999999999999");
833        assert!(result.is_err());
834    }
835
836    #[test]
837    fn test_move_u256_serialization() {
838        let val = MoveU256::from_u128(0x0102_0304_0506_0708);
839        let bcs = aptos_bcs::to_bytes(&val).unwrap();
840        // Should serialize as 32 bytes (tuple, not vector with length prefix)
841        assert_eq!(bcs.len(), 32);
842        // First 8 bytes should be our value in little-endian
843        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
844    }
845
846    #[test]
847    fn test_move_i128_new() {
848        let val = MoveI128::new(42);
849        assert_eq!(val.0, 42);
850    }
851
852    #[test]
853    fn test_move_i128_from_i128() {
854        let val: MoveI128 = (-100i128).into();
855        assert_eq!(val.0, -100);
856    }
857
858    #[test]
859    fn test_move_i128_serialization_positive() {
860        let val = MoveI128::new(0x0102_0304_0506_0708);
861        let bcs = aptos_bcs::to_bytes(&val).unwrap();
862        // Should serialize as 16 bytes (tuple, not vector)
863        assert_eq!(bcs.len(), 16);
864        // First 8 bytes should be our value in little-endian
865        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
866        // Upper 8 bytes should be zeros for positive value
867        assert_eq!(&bcs[8..], &[0, 0, 0, 0, 0, 0, 0, 0]);
868    }
869
870    #[test]
871    fn test_move_i128_serialization_negative() {
872        let val = MoveI128::new(-1);
873        let bcs = aptos_bcs::to_bytes(&val).unwrap();
874        assert_eq!(bcs.len(), 16);
875        // -1 in two's complement is all 0xFF bytes
876        assert_eq!(bcs, vec![0xFF; 16]);
877    }
878
879    #[test]
880    fn test_move_i256_from_i128_positive() {
881        let val = MoveI256::from_i128(42);
882        // First 16 bytes should contain the value
883        let expected = 42i128.to_le_bytes();
884        assert_eq!(&val.0[..16], &expected);
885        // Upper 16 bytes should be zeros for positive value
886        assert_eq!(&val.0[16..], &[0u8; 16]);
887    }
888
889    #[test]
890    fn test_move_i256_from_i128_negative() {
891        let val = MoveI256::from_i128(-1);
892        // -1 in two's complement should be all 0xFF bytes
893        assert_eq!(val.0, [0xFF; 32]);
894    }
895
896    #[test]
897    fn test_move_i256_from_le_bytes() {
898        let bytes = [0xcd; 32];
899        let val = MoveI256::from_le_bytes(bytes);
900        assert_eq!(val.0, bytes);
901    }
902
903    #[test]
904    fn test_move_i256_from_trait() {
905        let val: MoveI256 = (-100i128).into();
906        let expected = MoveI256::from_i128(-100);
907        assert_eq!(val, expected);
908    }
909
910    #[test]
911    fn test_move_i256_serialization() {
912        let val = MoveI256::from_i128(0x0102_0304_0506_0708);
913        let bcs = aptos_bcs::to_bytes(&val).unwrap();
914        // Should serialize as 32 bytes (tuple, not vector)
915        assert_eq!(bcs.len(), 32);
916        // First 8 bytes should be our value in little-endian
917        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
918    }
919
920    #[test]
921    fn test_move_i256_serialization_negative() {
922        let val = MoveI256::from_i128(-1);
923        let bcs = aptos_bcs::to_bytes(&val).unwrap();
924        assert_eq!(bcs.len(), 32);
925        // -1 in two's complement is all 0xFF bytes
926        assert_eq!(bcs, vec![0xFF; 32]);
927    }
928
929    #[test]
930    fn test_input_entry_function_data_new() {
931        let builder = InputEntryFunctionData::new("0x1::coin::transfer");
932        let result = builder.build();
933        // Should build successfully (no args required yet)
934        assert!(result.is_ok());
935    }
936
937    #[test]
938    fn test_input_entry_function_data_invalid_function_id() {
939        let builder = InputEntryFunctionData::new("invalid");
940        let result = builder.build();
941        assert!(result.is_err());
942        assert!(
943            result
944                .unwrap_err()
945                .to_string()
946                .contains("Invalid function ID")
947        );
948    }
949
950    #[test]
951    fn test_input_entry_function_data_type_arg() {
952        let builder = InputEntryFunctionData::new("0x1::coin::transfer")
953            .type_arg("0x1::aptos_coin::AptosCoin");
954        let result = builder.build();
955        assert!(result.is_ok());
956    }
957
958    #[test]
959    fn test_input_entry_function_data_invalid_type_arg() {
960        let builder =
961            InputEntryFunctionData::new("0x1::coin::transfer").type_arg("not a valid type");
962        let result = builder.build();
963        assert!(result.is_err());
964        assert!(result.unwrap_err().to_string().contains("type argument"));
965    }
966
967    #[test]
968    fn test_input_entry_function_data_type_arg_typed() {
969        use crate::types::TypeTag;
970
971        let builder =
972            InputEntryFunctionData::new("0x1::coin::transfer").type_arg_typed(TypeTag::U64);
973        let result = builder.build();
974        assert!(result.is_ok());
975    }
976
977    #[test]
978    fn test_input_entry_function_data_type_args() {
979        let builder = InputEntryFunctionData::new("0x1::coin::transfer").type_args(["u64", "u128"]);
980        let result = builder.build();
981        assert!(result.is_ok());
982    }
983
984    #[test]
985    fn test_input_entry_function_data_type_args_typed() {
986        use crate::types::TypeTag;
987
988        let builder = InputEntryFunctionData::new("0x1::coin::transfer")
989            .type_args_typed([TypeTag::U64, TypeTag::Bool]);
990        let result = builder.build();
991        assert!(result.is_ok());
992    }
993
994    #[test]
995    fn test_input_entry_function_data_arg() {
996        let builder = InputEntryFunctionData::new("0x1::coin::transfer")
997            .arg(42u64)
998            .arg(true)
999            .arg("hello".to_string());
1000        let result = builder.build();
1001        assert!(result.is_ok());
1002    }
1003
1004    #[test]
1005    fn test_input_entry_function_data_arg_raw() {
1006        let raw_bytes = vec![0x01, 0x02, 0x03];
1007        let builder = InputEntryFunctionData::new("0x1::coin::transfer").arg_raw(raw_bytes);
1008        let result = builder.build();
1009        assert!(result.is_ok());
1010    }
1011
1012    #[test]
1013    fn test_input_entry_function_data_args() {
1014        let builder = InputEntryFunctionData::new("0x1::coin::transfer").args([1u64, 2u64, 3u64]);
1015        let result = builder.build();
1016        assert!(result.is_ok());
1017    }
1018
1019    #[test]
1020    fn test_input_entry_function_data_transfer_apt() {
1021        use crate::types::AccountAddress;
1022
1023        let recipient = AccountAddress::from_hex("0x123").unwrap();
1024        let result = InputEntryFunctionData::transfer_apt(recipient, 1000);
1025        assert!(result.is_ok());
1026    }
1027
1028    #[test]
1029    fn test_input_entry_function_data_builder_debug() {
1030        let builder = InputEntryFunctionData::new("0x1::coin::transfer");
1031        let debug = format!("{builder:?}");
1032        assert!(debug.contains("InputEntryFunctionDataBuilder"));
1033    }
1034
1035    #[test]
1036    fn test_input_entry_function_data_builder_clone() {
1037        let builder = InputEntryFunctionData::new("0x1::coin::transfer").arg(42u64);
1038        let cloned = builder.clone();
1039        assert!(cloned.build().is_ok());
1040    }
1041
1042    #[test]
1043    fn test_move_u256_debug() {
1044        let val = MoveU256::from_u128(123_456_789);
1045        let debug = format!("{val:?}");
1046        assert!(debug.contains("MoveU256"));
1047    }
1048
1049    #[test]
1050    fn test_move_i128_debug() {
1051        let val = MoveI128::new(-42);
1052        let debug = format!("{val:?}");
1053        assert!(debug.contains("MoveI128"));
1054    }
1055
1056    #[test]
1057    fn test_move_i256_debug() {
1058        let val = MoveI256::from_i128(-42);
1059        let debug = format!("{val:?}");
1060        assert!(debug.contains("MoveI256"));
1061    }
1062
1063    #[test]
1064    fn test_move_u256_equality() {
1065        let val1 = MoveU256::from_u128(100);
1066        let val2 = MoveU256::from_u128(100);
1067        let val3 = MoveU256::from_u128(200);
1068        assert_eq!(val1, val2);
1069        assert_ne!(val1, val3);
1070    }
1071
1072    #[test]
1073    fn test_move_i256_equality() {
1074        let val1 = MoveI256::from_i128(-50);
1075        let val2 = MoveI256::from_i128(-50);
1076        let val3 = MoveI256::from_i128(50);
1077        assert_eq!(val1, val2);
1078        assert_ne!(val1, val3);
1079    }
1080
1081    #[test]
1082    fn test_move_u256_clone() {
1083        let val1 = MoveU256::from_u128(999);
1084        let val2 = val1;
1085        assert_eq!(val1, val2);
1086    }
1087
1088    #[test]
1089    fn test_move_i256_clone() {
1090        let val1 = MoveI256::from_i128(-999);
1091        let val2 = val1;
1092        assert_eq!(val1, val2);
1093    }
1094}