Skip to main content

aptos_sdk/types/
move_types.rs

1//! Move type system representations.
2//!
3//! This module provides Rust types that mirror the Move type system,
4//! including type tags, struct tags, and move values.
5//!
6//! # Security
7//!
8//! All parsing functions enforce length limits to prevent denial-of-service
9//! attacks via excessive memory allocation or CPU usage.
10
11use crate::error::{AptosError, AptosResult};
12use crate::types::AccountAddress;
13use serde::{Deserialize, Serialize};
14use std::fmt;
15use std::str::FromStr;
16
17/// Maximum length for type tag strings to prevent `DoS` via excessive parsing.
18/// This limit is generous enough for any realistic type tag while preventing abuse.
19const MAX_TYPE_TAG_LENGTH: usize = 1024;
20
21/// Maximum length for identifier strings.
22const MAX_IDENTIFIER_LENGTH: usize = 128;
23
24/// Maximum depth for nested type arguments (e.g., vector<vector<vector<...>>>).
25const MAX_TYPE_NESTING_DEPTH: usize = 8;
26
27/// An identifier in Move (module name, function name, etc.).
28///
29/// Identifiers must start with a letter or underscore and contain
30/// only alphanumeric characters and underscores.
31#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[serde(transparent)]
33pub struct Identifier(String);
34
35impl Identifier {
36    /// Creates a new identifier, validating the format.
37    ///
38    /// # Security
39    ///
40    /// This function enforces a length limit of 128 bytes to prevent
41    /// denial-of-service attacks via excessive memory allocation. Identifiers
42    /// are ASCII, so this equals 128 characters.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if the identifier is empty, exceeds 128 bytes, does not start
47    /// with a letter or underscore, or contains characters that are not alphanumeric or underscore.
48    pub fn new(s: impl Into<String>) -> AptosResult<Self> {
49        let s = s.into();
50        // Security: enforce length limit to prevent DoS
51        if s.len() > MAX_IDENTIFIER_LENGTH {
52            return Err(AptosError::InvalidTypeTag(format!(
53                "identifier too long: {} bytes (max {})",
54                s.len(),
55                MAX_IDENTIFIER_LENGTH
56            )));
57        }
58        let maybe_first = s.chars().next();
59        let Some(first) = maybe_first else {
60            return Err(AptosError::InvalidTypeTag(
61                "identifier cannot be empty".into(),
62            ));
63        };
64
65        if !first.is_ascii_alphabetic() && first != '_' {
66            return Err(AptosError::InvalidTypeTag(format!(
67                "identifier must start with letter or underscore: {s}"
68            )));
69        }
70        if !s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
71            return Err(AptosError::InvalidTypeTag(format!(
72                "identifier contains invalid characters: {s}"
73            )));
74        }
75        Ok(Self(s))
76    }
77
78    /// Creates an identifier without validation (for internal use).
79    pub(crate) fn from_string_unchecked(s: String) -> Self {
80        Self(s)
81    }
82
83    /// Returns the identifier as a string slice.
84    pub fn as_str(&self) -> &str {
85        &self.0
86    }
87}
88
89impl fmt::Display for Identifier {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(f, "{}", self.0)
92    }
93}
94
95impl FromStr for Identifier {
96    type Err = AptosError;
97
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        Self::new(s)
100    }
101}
102
103/// A Move module identifier (`address::module_name`).
104#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
105pub struct MoveModuleId {
106    /// The address where the module is published.
107    pub address: AccountAddress,
108    /// The name of the module.
109    pub name: Identifier,
110}
111
112impl MoveModuleId {
113    /// Creates a new module ID.
114    pub fn new(address: AccountAddress, name: Identifier) -> Self {
115        Self { address, name }
116    }
117
118    /// Parses a module ID from a string (e.g., "`0x1::coin`").
119    ///
120    /// # Errors
121    ///
122    /// Returns an error if the string is not in the format `address::module_name`, the address
123    /// is invalid, or the module name is not a valid identifier.
124    pub fn from_str_strict(s: &str) -> AptosResult<Self> {
125        let parts: Vec<&str> = s.split("::").collect();
126        if parts.len() != 2 {
127            return Err(AptosError::InvalidTypeTag(format!(
128                "invalid module ID format: {s}"
129            )));
130        }
131        let address = AccountAddress::from_str(parts[0])?;
132        let name = Identifier::new(parts[1])?;
133        Ok(Self { address, name })
134    }
135}
136
137impl fmt::Display for MoveModuleId {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(f, "{}::{}", self.address.to_short_string(), self.name)
140    }
141}
142
143impl FromStr for MoveModuleId {
144    type Err = AptosError;
145
146    fn from_str(s: &str) -> Result<Self, Self::Err> {
147        Self::from_str_strict(s)
148    }
149}
150
151/// A struct tag identifies a specific struct type in Move.
152///
153/// Format: `address::module::StructName<TypeArg1, TypeArg2, ...>`
154#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
155pub struct StructTag {
156    /// The address where the module is published.
157    pub address: AccountAddress,
158    /// The module name.
159    pub module: Identifier,
160    /// The struct name.
161    pub name: Identifier,
162    /// Type arguments (for generic structs).
163    #[serde(default)]
164    pub type_args: Vec<TypeTag>,
165}
166
167impl StructTag {
168    /// Creates a new struct tag.
169    pub fn new(
170        address: AccountAddress,
171        module: Identifier,
172        name: Identifier,
173        type_args: Vec<TypeTag>,
174    ) -> Self {
175        Self {
176            address,
177            module,
178            name,
179            type_args,
180        }
181    }
182
183    /// Creates a struct tag with no type arguments.
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if the module or name is not a valid identifier.
188    pub fn simple(
189        address: AccountAddress,
190        module: impl Into<String>,
191        name: impl Into<String>,
192    ) -> AptosResult<Self> {
193        Ok(Self {
194            address,
195            module: Identifier::new(module)?,
196            name: Identifier::new(name)?,
197            type_args: vec![],
198        })
199    }
200
201    /// The `AptosCoin` struct tag (`0x1::aptos_coin::AptosCoin`).
202    pub fn aptos_coin() -> Self {
203        Self {
204            address: AccountAddress::ONE,
205            module: Identifier::from_string_unchecked("aptos_coin".to_string()),
206            name: Identifier::from_string_unchecked("AptosCoin".to_string()),
207            type_args: vec![],
208        }
209    }
210}
211
212impl fmt::Display for StructTag {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        write!(
215            f,
216            "{}::{}::{}",
217            self.address.to_short_string(),
218            self.module,
219            self.name
220        )?;
221        if !self.type_args.is_empty() {
222            write!(f, "<")?;
223            for (i, arg) in self.type_args.iter().enumerate() {
224                if i > 0 {
225                    write!(f, ", ")?;
226                }
227                write!(f, "{arg}")?;
228            }
229            write!(f, ">")?;
230        }
231        Ok(())
232    }
233}
234
235/// Alias for `StructTag` used in some API responses.
236pub type MoveStructTag = StructTag;
237
238/// A type tag represents a Move type.
239///
240/// Type tags are used to specify types in entry function calls and
241/// to describe the types of resources and values.
242///
243/// Note: Variant indices must match Move core for BCS compatibility:
244/// - 0: Bool
245/// - 1: U8
246/// - 2: U64
247/// - 3: U128
248/// - 4: Address
249/// - 5: Signer
250/// - 6: Vector
251/// - 7: Struct
252/// - 8: U16 (added later)
253/// - 9: U32 (added later)
254/// - 10: U256 (added later)
255#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
256#[serde(rename_all = "lowercase")]
257pub enum TypeTag {
258    /// Boolean type (variant 0)
259    Bool,
260    /// 8-bit unsigned integer (variant 1)
261    U8,
262    /// 64-bit unsigned integer (variant 2)
263    U64,
264    /// 128-bit unsigned integer (variant 3)
265    U128,
266    /// Address type (variant 4)
267    Address,
268    /// Signer type (variant 5, only valid in certain contexts)
269    Signer,
270    /// Vector type with element type (variant 6)
271    Vector(Box<TypeTag>),
272    /// Struct type (variant 7)
273    Struct(Box<StructTag>),
274    /// 16-bit unsigned integer (variant 8, added later)
275    U16,
276    /// 32-bit unsigned integer (variant 9, added later)
277    U32,
278    /// 256-bit unsigned integer (variant 10, added later)
279    U256,
280    // Signed integer types (BCS indices 11-16).
281    //
282    // NOTE: Move has no signed integer types today. No current Aptos network
283    // (mainnet, testnet, devnet, or localnet) supports these, and no node can
284    // deserialize BCS variant indices 11-16, so they will never appear in a
285    // real transaction or resource. They are kept only so the variant indices
286    // of the unsigned/reference types above stay stable; removing them would
287    // be a breaking change. Do not rely on them.
288    /// 8-bit signed integer (variant 11; not supported by any Aptos network)
289    I8,
290    /// 16-bit signed integer (variant 12)
291    I16,
292    /// 32-bit signed integer (variant 13)
293    I32,
294    /// 64-bit signed integer (variant 14)
295    I64,
296    /// 128-bit signed integer (variant 15)
297    I128,
298    /// 256-bit signed integer (variant 16)
299    I256,
300}
301
302impl TypeTag {
303    /// Creates a vector type tag with the given element type.
304    pub fn vector(element: TypeTag) -> Self {
305        Self::Vector(Box::new(element))
306    }
307
308    /// Creates a struct type tag.
309    pub fn struct_tag(tag: StructTag) -> Self {
310        Self::Struct(Box::new(tag))
311    }
312
313    /// Returns the `AptosCoin` type tag (`0x1::aptos_coin::AptosCoin`).
314    pub fn aptos_coin() -> Self {
315        Self::Struct(Box::new(StructTag::aptos_coin()))
316    }
317
318    /// Parses a type tag from a string.
319    ///
320    /// Supports:
321    /// - Primitive types: bool, u8, u16, u32, u64, u128, u256, address, signer
322    /// - Struct types: `address::module::StructName`
323    /// - Vector types: vector<`element_type`>
324    /// - Generic struct types: `address::module::StructName`<`TypeArg1`, `TypeArg2`>
325    ///
326    /// # Security
327    ///
328    /// This function enforces length and depth limits to prevent denial-of-service
329    /// attacks via excessive parsing or memory allocation.
330    ///
331    /// # Errors
332    ///
333    /// Returns an error if the type tag string exceeds 1024 bytes, has excessive nesting
334    /// depth (more than 8 levels), contains invalid syntax, or any component (address, module,
335    /// struct name, or type arguments) is invalid.
336    ///
337    /// # Example
338    ///
339    /// ```rust
340    /// use aptos_sdk::types::TypeTag;
341    ///
342    /// let tag = TypeTag::from_str_strict("0x1::aptos_coin::AptosCoin").unwrap();
343    /// let tag = TypeTag::from_str_strict("u64").unwrap();
344    /// let tag = TypeTag::from_str_strict("vector<u8>").unwrap();
345    /// ```
346    pub fn from_str_strict(s: &str) -> AptosResult<Self> {
347        let s = s.trim();
348
349        // Security: enforce length limit to prevent DoS
350        if s.len() > MAX_TYPE_TAG_LENGTH {
351            return Err(AptosError::InvalidTypeTag(format!(
352                "type tag too long: {} bytes (max {})",
353                s.len(),
354                MAX_TYPE_TAG_LENGTH
355            )));
356        }
357
358        Self::parse_type_tag_with_depth(s, 0)
359    }
360
361    /// Internal parser with depth tracking to prevent stack overflow.
362    fn parse_type_tag_with_depth(s: &str, depth: usize) -> AptosResult<Self> {
363        // Security: prevent excessive nesting depth
364        if depth > MAX_TYPE_NESTING_DEPTH {
365            return Err(AptosError::InvalidTypeTag(format!(
366                "type tag nesting too deep: {depth} levels (max {MAX_TYPE_NESTING_DEPTH})"
367            )));
368        }
369
370        // Check primitive types first
371        match s {
372            "bool" => return Ok(TypeTag::Bool),
373            "u8" => return Ok(TypeTag::U8),
374            "u16" => return Ok(TypeTag::U16),
375            "u32" => return Ok(TypeTag::U32),
376            "u64" => return Ok(TypeTag::U64),
377            "u128" => return Ok(TypeTag::U128),
378            "u256" => return Ok(TypeTag::U256),
379            "i8" => return Ok(TypeTag::I8),
380            "i16" => return Ok(TypeTag::I16),
381            "i32" => return Ok(TypeTag::I32),
382            "i64" => return Ok(TypeTag::I64),
383            "i128" => return Ok(TypeTag::I128),
384            "i256" => return Ok(TypeTag::I256),
385            "address" => return Ok(TypeTag::Address),
386            "signer" => return Ok(TypeTag::Signer),
387            _ => {}
388        }
389
390        // Check for vector type. Trim the inner type so whitespace is handled
391        // consistently with struct type arguments (e.g. `vector< u8 >`).
392        if s.starts_with("vector<") && s.ends_with('>') {
393            let inner = s[7..s.len() - 1].trim();
394            let inner_tag = Self::parse_type_tag_with_depth(inner, depth + 1)?;
395            return Ok(TypeTag::Vector(Box::new(inner_tag)));
396        }
397
398        // Parse as struct type (address::module::name or with generics)
399        Self::parse_struct_type_with_depth(s, depth)
400    }
401
402    /// Parses a struct type tag with depth tracking.
403    fn parse_struct_type_with_depth(s: &str, depth: usize) -> AptosResult<Self> {
404        // Find the opening < for generics (if any)
405        let generic_start = s.find('<');
406
407        let (base, type_args_str) = if let Some(idx) = generic_start {
408            if !s.ends_with('>') {
409                return Err(AptosError::InvalidTypeTag(format!(
410                    "malformed generic type: {s}"
411                )));
412            }
413            (&s[..idx], Some(&s[idx + 1..s.len() - 1]))
414        } else {
415            (s, None)
416        };
417
418        // Parse the base struct (address::module::name)
419        let parts: Vec<&str> = base.split("::").collect();
420        if parts.len() != 3 {
421            return Err(AptosError::InvalidTypeTag(format!(
422                "invalid struct type format (expected address::module::name): {s}"
423            )));
424        }
425
426        let address = AccountAddress::from_str(parts[0])?;
427        let module = Identifier::new(parts[1])?;
428        let name = Identifier::new(parts[2])?;
429
430        // Parse type arguments if present
431        let type_args = if let Some(args_str) = type_args_str {
432            Self::parse_type_args_with_depth(args_str, depth)?
433        } else {
434            vec![]
435        };
436
437        Ok(TypeTag::Struct(Box::new(StructTag {
438            address,
439            module,
440            name,
441            type_args,
442        })))
443    }
444
445    /// Parses comma-separated type arguments with depth tracking.
446    ///
447    /// This is only called when a generic argument list (`<...>`) is present,
448    /// so every entry between the angle brackets must be a non-empty type. An
449    /// empty list (`<>`), an empty entry between commas (`<u8,,u16>`), a
450    /// leading comma (`<,u8>`), or a trailing comma (`<u8,>`) is rejected as
451    /// malformed rather than silently dropped.
452    fn parse_type_args_with_depth(s: &str, depth: usize) -> AptosResult<Vec<TypeTag>> {
453        let mut result = Vec::new();
454        let mut bracket_depth: i32 = 0;
455        let mut start = 0;
456
457        // Parse and push a single (trimmed) argument, rejecting empty entries.
458        let push_arg = |arg: &str, result: &mut Vec<TypeTag>| -> AptosResult<()> {
459            let arg = arg.trim();
460            if arg.is_empty() {
461                return Err(AptosError::InvalidTypeTag(
462                    "empty type argument in generic type".into(),
463                ));
464            }
465            result.push(Self::parse_type_tag_with_depth(arg, depth + 1)?);
466            Ok(())
467        };
468
469        for (i, c) in s.char_indices() {
470            match c {
471                '<' => bracket_depth += 1,
472                '>' => bracket_depth -= 1,
473                ',' if bracket_depth == 0 => {
474                    push_arg(&s[start..i], &mut result)?;
475                    start = i + 1;
476                }
477                _ => {}
478            }
479        }
480
481        // Handle the final argument (also catches empty `<>` and trailing `,`).
482        push_arg(&s[start..], &mut result)?;
483
484        Ok(result)
485    }
486}
487
488impl fmt::Display for TypeTag {
489    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490        match self {
491            TypeTag::Bool => write!(f, "bool"),
492            TypeTag::U8 => write!(f, "u8"),
493            TypeTag::U16 => write!(f, "u16"),
494            TypeTag::U32 => write!(f, "u32"),
495            TypeTag::U64 => write!(f, "u64"),
496            TypeTag::U128 => write!(f, "u128"),
497            TypeTag::U256 => write!(f, "u256"),
498            TypeTag::I8 => write!(f, "i8"),
499            TypeTag::I16 => write!(f, "i16"),
500            TypeTag::I32 => write!(f, "i32"),
501            TypeTag::I64 => write!(f, "i64"),
502            TypeTag::I128 => write!(f, "i128"),
503            TypeTag::I256 => write!(f, "i256"),
504            TypeTag::Address => write!(f, "address"),
505            TypeTag::Signer => write!(f, "signer"),
506            TypeTag::Vector(inner) => write!(f, "vector<{inner}>"),
507            TypeTag::Struct(tag) => write!(f, "{tag}"),
508        }
509    }
510}
511
512/// An entry function identifier (`address::module::function`).
513#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
514pub struct EntryFunctionId {
515    /// The module containing the function.
516    pub module: MoveModuleId,
517    /// The function name.
518    pub name: Identifier,
519}
520
521impl EntryFunctionId {
522    /// Creates a new entry function ID.
523    pub fn new(module: MoveModuleId, name: Identifier) -> Self {
524        Self { module, name }
525    }
526
527    /// Parses an entry function ID from a string (e.g., "`0x1::coin::transfer`").
528    ///
529    /// # Errors
530    ///
531    /// Returns an error if the string is not in the format `address::module::function`, the address
532    /// is invalid, or the module or function name is not a valid identifier.
533    pub fn from_str_strict(s: &str) -> AptosResult<Self> {
534        let parts: Vec<&str> = s.split("::").collect();
535        if parts.len() != 3 {
536            return Err(AptosError::InvalidTypeTag(format!(
537                "invalid entry function ID format: {s}"
538            )));
539        }
540        let address = AccountAddress::from_str(parts[0])?;
541        let module = Identifier::new(parts[1])?;
542        let name = Identifier::new(parts[2])?;
543        Ok(Self {
544            module: MoveModuleId::new(address, module),
545            name,
546        })
547    }
548}
549
550impl fmt::Display for EntryFunctionId {
551    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
552        write!(f, "{}::{}", self.module, self.name)
553    }
554}
555
556impl FromStr for EntryFunctionId {
557    type Err = AptosError;
558
559    fn from_str(s: &str) -> Result<Self, Self::Err> {
560        Self::from_str_strict(s)
561    }
562}
563
564/// A Move struct type from the API (with string representation).
565#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
566pub struct MoveType(String);
567
568impl MoveType {
569    /// Creates a new `MoveType` from a string.
570    pub fn new(s: impl Into<String>) -> Self {
571        Self(s.into())
572    }
573
574    /// Returns the type as a string.
575    pub fn as_str(&self) -> &str {
576        &self.0
577    }
578}
579
580impl fmt::Display for MoveType {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        write!(f, "{}", self.0)
583    }
584}
585
586/// A Move resource (struct value with abilities).
587#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
588pub struct MoveResource {
589    /// The type of this resource.
590    #[serde(rename = "type")]
591    pub typ: String,
592    /// The data contained in this resource.
593    pub data: serde_json::Value,
594}
595
596/// A Move struct value.
597#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
598pub struct MoveStruct {
599    /// The fields of the struct as a JSON object.
600    #[serde(flatten)]
601    pub fields: serde_json::Map<String, serde_json::Value>,
602}
603
604/// A Move value (for view function returns, etc.).
605#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
606#[serde(untagged)]
607pub enum MoveValue {
608    /// A boolean value.
609    Bool(bool),
610    /// An integer value (stored as string for large numbers).
611    Number(String),
612    /// A string value.
613    String(String),
614    /// An address value.
615    Address(AccountAddress),
616    /// A vector value.
617    Vector(Vec<MoveValue>),
618    /// A struct value.
619    Struct(MoveStruct),
620    /// A null/unit value.
621    Null,
622}
623
624impl MoveValue {
625    /// Tries to extract a boolean value.
626    pub fn as_bool(&self) -> Option<bool> {
627        match self {
628            MoveValue::Bool(b) => Some(*b),
629            _ => None,
630        }
631    }
632
633    /// Tries to extract a u64 value.
634    pub fn as_u64(&self) -> Option<u64> {
635        match self {
636            MoveValue::Number(s) => s.parse().ok(),
637            _ => None,
638        }
639    }
640
641    /// Tries to extract a u128 value.
642    pub fn as_u128(&self) -> Option<u128> {
643        match self {
644            MoveValue::Number(s) => s.parse().ok(),
645            _ => None,
646        }
647    }
648
649    /// Tries to extract a string value.
650    pub fn as_str(&self) -> Option<&str> {
651        match self {
652            MoveValue::String(s) | MoveValue::Number(s) => Some(s),
653            _ => None,
654        }
655    }
656
657    /// Tries to extract an address value.
658    pub fn as_address(&self) -> Option<&AccountAddress> {
659        match self {
660            MoveValue::Address(a) => Some(a),
661            _ => None,
662        }
663    }
664
665    /// Tries to extract a vector value.
666    pub fn as_vec(&self) -> Option<&[MoveValue]> {
667        match self {
668            MoveValue::Vector(v) => Some(v),
669            _ => None,
670        }
671    }
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677
678    #[test]
679    fn test_identifier() {
680        assert!(Identifier::new("hello").is_ok());
681        assert!(Identifier::new("_private").is_ok());
682        assert!(Identifier::new("CamelCase123").is_ok());
683        assert!(Identifier::new("").is_err());
684        assert!(Identifier::new("123start").is_err());
685        assert!(Identifier::new("has-dash").is_err());
686    }
687
688    #[test]
689    fn test_identifier_as_str() {
690        let id = Identifier::new("test").unwrap();
691        assert_eq!(id.as_str(), "test");
692    }
693
694    #[test]
695    fn test_identifier_display() {
696        let id = Identifier::new("my_func").unwrap();
697        assert_eq!(format!("{id}"), "my_func");
698    }
699
700    #[test]
701    fn test_module_id() {
702        let module_id = MoveModuleId::from_str_strict("0x1::coin").unwrap();
703        assert_eq!(module_id.address, AccountAddress::ONE);
704        assert_eq!(module_id.name.as_str(), "coin");
705        assert_eq!(module_id.to_string(), "0x1::coin");
706    }
707
708    #[test]
709    fn test_module_id_invalid() {
710        assert!(MoveModuleId::from_str_strict("invalid").is_err());
711        assert!(MoveModuleId::from_str_strict("0x1").is_err());
712        assert!(MoveModuleId::from_str_strict("0x1::").is_err());
713    }
714
715    #[test]
716    fn test_struct_tag() {
717        let tag = StructTag::aptos_coin();
718        assert_eq!(tag.to_string(), "0x1::aptos_coin::AptosCoin");
719    }
720
721    #[test]
722    fn test_struct_tag_with_type_args() {
723        let coin_store = StructTag::new(
724            AccountAddress::ONE,
725            Identifier::new("coin").unwrap(),
726            Identifier::new("CoinStore").unwrap(),
727            vec![TypeTag::aptos_coin()],
728        );
729        assert!(coin_store.to_string().contains("CoinStore"));
730        assert!(coin_store.to_string().contains("AptosCoin"));
731    }
732
733    #[test]
734    fn test_struct_tag_aptos_coin() {
735        let tag = StructTag::aptos_coin();
736        assert_eq!(tag.address, AccountAddress::ONE);
737        assert_eq!(tag.module.as_str(), "aptos_coin");
738        assert_eq!(tag.name.as_str(), "AptosCoin");
739    }
740
741    #[test]
742    fn test_type_tag_display() {
743        assert_eq!(TypeTag::Bool.to_string(), "bool");
744        assert_eq!(TypeTag::U8.to_string(), "u8");
745        assert_eq!(TypeTag::U16.to_string(), "u16");
746        assert_eq!(TypeTag::U32.to_string(), "u32");
747        assert_eq!(TypeTag::U64.to_string(), "u64");
748        assert_eq!(TypeTag::U128.to_string(), "u128");
749        assert_eq!(TypeTag::U256.to_string(), "u256");
750        assert_eq!(TypeTag::Address.to_string(), "address");
751        assert_eq!(TypeTag::Signer.to_string(), "signer");
752        assert_eq!(TypeTag::vector(TypeTag::U8).to_string(), "vector<u8>");
753        assert_eq!(
754            TypeTag::aptos_coin().to_string(),
755            "0x1::aptos_coin::AptosCoin"
756        );
757    }
758
759    #[test]
760    fn test_type_tag_from_str_strict() {
761        assert_eq!(TypeTag::from_str_strict("bool").unwrap(), TypeTag::Bool);
762        assert_eq!(TypeTag::from_str_strict("u8").unwrap(), TypeTag::U8);
763        assert_eq!(TypeTag::from_str_strict("u64").unwrap(), TypeTag::U64);
764        assert_eq!(
765            TypeTag::from_str_strict("address").unwrap(),
766            TypeTag::Address
767        );
768        assert_eq!(TypeTag::from_str_strict("signer").unwrap(), TypeTag::Signer);
769    }
770
771    #[test]
772    fn test_type_tag_from_str_struct() {
773        let tag = TypeTag::from_str_strict("0x1::aptos_coin::AptosCoin").unwrap();
774        if let TypeTag::Struct(s) = tag {
775            assert_eq!(s.name.as_str(), "AptosCoin");
776        } else {
777            panic!("Expected struct type tag");
778        }
779    }
780
781    #[test]
782    fn test_type_tag_from_str_vector() {
783        let tag = TypeTag::from_str_strict("vector<u8>").unwrap();
784        if let TypeTag::Vector(inner) = tag {
785            assert_eq!(*inner, TypeTag::U8);
786        } else {
787            panic!("Expected vector type tag");
788        }
789    }
790
791    #[test]
792    fn test_type_tag_nested_vector() {
793        let tag = TypeTag::from_str_strict("vector<vector<u64>>").unwrap();
794        if let TypeTag::Vector(outer) = tag {
795            if let TypeTag::Vector(inner) = *outer {
796                assert_eq!(*inner, TypeTag::U64);
797            } else {
798                panic!("Expected nested vector");
799            }
800        } else {
801            panic!("Expected vector type tag");
802        }
803    }
804
805    #[test]
806    fn test_type_tag_invalid() {
807        assert!(TypeTag::from_str_strict("invalid").is_err());
808        assert!(TypeTag::from_str_strict("vector<").is_err());
809    }
810
811    #[test]
812    fn test_entry_function_id() {
813        let func = EntryFunctionId::from_str_strict("0x1::coin::transfer").unwrap();
814        assert_eq!(func.to_string(), "0x1::coin::transfer");
815    }
816
817    #[test]
818    fn test_entry_function_id_invalid() {
819        assert!(EntryFunctionId::from_str_strict("0x1::coin").is_err());
820        assert!(EntryFunctionId::from_str_strict("invalid").is_err());
821    }
822
823    #[test]
824    fn test_move_value_as_bool() {
825        let val = MoveValue::Bool(true);
826        assert_eq!(val.as_bool(), Some(true));
827        assert!(MoveValue::Number("123".to_string()).as_bool().is_none());
828    }
829
830    #[test]
831    fn test_move_value_as_u64() {
832        let val = MoveValue::Number("12345".to_string());
833        assert_eq!(val.as_u64(), Some(12345));
834        assert!(MoveValue::Bool(true).as_u64().is_none());
835    }
836
837    #[test]
838    fn test_move_value_as_u128() {
839        let val = MoveValue::Number("340282366920938463463374607431768211455".to_string());
840        assert_eq!(val.as_u128(), Some(u128::MAX));
841    }
842
843    #[test]
844    fn test_move_value_as_str() {
845        let val = MoveValue::String("hello".to_string());
846        assert_eq!(val.as_str(), Some("hello"));
847        let num = MoveValue::Number("123".to_string());
848        assert_eq!(num.as_str(), Some("123"));
849    }
850
851    #[test]
852    fn test_move_value_as_address() {
853        let val = MoveValue::Address(AccountAddress::ONE);
854        assert_eq!(val.as_address(), Some(&AccountAddress::ONE));
855        assert!(MoveValue::Bool(true).as_address().is_none());
856    }
857
858    #[test]
859    fn test_move_value_as_vec() {
860        let val = MoveValue::Vector(vec![MoveValue::Bool(true), MoveValue::Bool(false)]);
861        let vec = val.as_vec().unwrap();
862        assert_eq!(vec.len(), 2);
863    }
864
865    #[test]
866    fn test_move_resource_deserialization() {
867        let json = r#"{
868            "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
869            "data": {"coin": {"value": "1000"}}
870        }"#;
871        let resource: MoveResource = serde_json::from_str(json).unwrap();
872        assert_eq!(
873            resource.typ,
874            "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
875        );
876    }
877
878    #[test]
879    fn test_identifier_bcs_serialization() {
880        let id = Identifier::new("test_function").unwrap();
881        let serialized = aptos_bcs::to_bytes(&id).unwrap();
882        let deserialized: Identifier = aptos_bcs::from_bytes(&serialized).unwrap();
883        assert_eq!(id, deserialized);
884    }
885
886    #[test]
887    fn test_module_id_new() {
888        let module =
889            MoveModuleId::new(AccountAddress::ONE, Identifier::new("test_module").unwrap());
890        assert_eq!(module.address, AccountAddress::ONE);
891        assert_eq!(module.name.as_str(), "test_module");
892    }
893
894    #[test]
895    fn test_module_id_bcs_serialization() {
896        let module = MoveModuleId::from_str_strict("0x1::coin").unwrap();
897        let serialized = aptos_bcs::to_bytes(&module).unwrap();
898        let deserialized: MoveModuleId = aptos_bcs::from_bytes(&serialized).unwrap();
899        assert_eq!(module, deserialized);
900    }
901
902    #[test]
903    fn test_struct_tag_new() {
904        let tag = StructTag::new(
905            AccountAddress::from_hex("0x123").unwrap(),
906            Identifier::new("my_module").unwrap(),
907            Identifier::new("MyStruct").unwrap(),
908            vec![TypeTag::U64, TypeTag::Bool],
909        );
910        assert_eq!(tag.address, AccountAddress::from_hex("0x123").unwrap());
911        assert_eq!(tag.module.as_str(), "my_module");
912        assert_eq!(tag.name.as_str(), "MyStruct");
913        assert_eq!(tag.type_args.len(), 2);
914    }
915
916    #[test]
917    fn test_struct_tag_display_with_type_args() {
918        let tag = StructTag::new(
919            AccountAddress::ONE,
920            Identifier::new("coin").unwrap(),
921            Identifier::new("CoinStore").unwrap(),
922            vec![TypeTag::aptos_coin()],
923        );
924        let display = tag.to_string();
925        assert!(display.contains("CoinStore"));
926        assert!(display.contains('<'));
927        assert!(display.contains('>'));
928    }
929
930    #[test]
931    fn test_struct_tag_bcs_serialization() {
932        let tag = StructTag::aptos_coin();
933        let serialized = aptos_bcs::to_bytes(&tag).unwrap();
934        let deserialized: StructTag = aptos_bcs::from_bytes(&serialized).unwrap();
935        assert_eq!(tag, deserialized);
936    }
937
938    #[test]
939    fn test_type_tag_vector_constructor() {
940        let vec_type = TypeTag::vector(TypeTag::Address);
941        if let TypeTag::Vector(inner) = vec_type {
942            assert_eq!(*inner, TypeTag::Address);
943        } else {
944            panic!("Expected Vector type");
945        }
946    }
947
948    #[test]
949    fn test_type_tag_struct_constructor() {
950        let struct_type = TypeTag::struct_tag(StructTag::aptos_coin());
951        if let TypeTag::Struct(s) = struct_type {
952            assert_eq!(s.name.as_str(), "AptosCoin");
953        } else {
954            panic!("Expected Struct type");
955        }
956    }
957
958    #[test]
959    fn test_type_tag_from_str_u16_u32_u256() {
960        assert_eq!(TypeTag::from_str_strict("u16").unwrap(), TypeTag::U16);
961        assert_eq!(TypeTag::from_str_strict("u32").unwrap(), TypeTag::U32);
962        assert_eq!(TypeTag::from_str_strict("u256").unwrap(), TypeTag::U256);
963    }
964
965    #[test]
966    fn test_type_tag_from_str_vector_of_struct() {
967        let tag = TypeTag::from_str_strict("vector<0x1::aptos_coin::AptosCoin>").unwrap();
968        if let TypeTag::Vector(inner) = tag {
969            if let TypeTag::Struct(s) = *inner {
970                assert_eq!(s.name.as_str(), "AptosCoin");
971            } else {
972                panic!("Expected Struct inside Vector");
973            }
974        } else {
975            panic!("Expected Vector type");
976        }
977    }
978
979    #[test]
980    fn test_type_tag_from_str_struct_with_multiple_type_args() {
981        let tag = TypeTag::from_str_strict("0x1::table::Table<address, u64>").unwrap();
982        if let TypeTag::Struct(s) = tag {
983            assert_eq!(s.name.as_str(), "Table");
984            assert_eq!(s.type_args.len(), 2);
985            assert_eq!(s.type_args[0], TypeTag::Address);
986            assert_eq!(s.type_args[1], TypeTag::U64);
987        } else {
988            panic!("Expected Struct type");
989        }
990    }
991
992    #[test]
993    fn test_type_tag_from_str_malformed_generic() {
994        // Missing closing bracket
995        assert!(TypeTag::from_str_strict("vector<u8").is_err());
996        // Missing opening bracket
997        assert!(TypeTag::from_str_strict("vectoru8>").is_err());
998        // Malformed struct generic
999        assert!(TypeTag::from_str_strict("0x1::coin::Store<u64").is_err());
1000    }
1001
1002    #[test]
1003    fn test_type_tag_bcs_serialization() {
1004        let types = vec![
1005            TypeTag::Bool,
1006            TypeTag::U8,
1007            TypeTag::U16,
1008            TypeTag::U32,
1009            TypeTag::U64,
1010            TypeTag::U128,
1011            TypeTag::U256,
1012            TypeTag::Address,
1013            TypeTag::Signer,
1014            TypeTag::vector(TypeTag::U8),
1015            TypeTag::aptos_coin(),
1016        ];
1017
1018        for t in types {
1019            let serialized = aptos_bcs::to_bytes(&t).unwrap();
1020            let deserialized: TypeTag = aptos_bcs::from_bytes(&serialized).unwrap();
1021            assert_eq!(t, deserialized);
1022        }
1023    }
1024
1025    #[test]
1026    fn test_entry_function_id_new() {
1027        let module = MoveModuleId::from_str_strict("0x1::coin").unwrap();
1028        let name = Identifier::new("transfer").unwrap();
1029        let func = EntryFunctionId::new(module, name);
1030        assert_eq!(func.to_string(), "0x1::coin::transfer");
1031    }
1032
1033    #[test]
1034    fn test_entry_function_id_from_str() {
1035        let func: EntryFunctionId = "0x1::coin::transfer".parse().unwrap();
1036        assert_eq!(func.module.address, AccountAddress::ONE);
1037        assert_eq!(func.module.name.as_str(), "coin");
1038        assert_eq!(func.name.as_str(), "transfer");
1039    }
1040
1041    #[test]
1042    fn test_move_type_new_and_as_str() {
1043        let t = MoveType::new("0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>");
1044        assert_eq!(
1045            t.as_str(),
1046            "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
1047        );
1048    }
1049
1050    #[test]
1051    fn test_move_type_display() {
1052        let t = MoveType::new("bool");
1053        assert_eq!(format!("{t}"), "bool");
1054    }
1055
1056    #[test]
1057    fn test_move_struct_fields() {
1058        let mut fields = serde_json::Map::new();
1059        fields.insert("value".to_string(), serde_json::json!("1000"));
1060        let s = MoveStruct { fields };
1061        assert_eq!(s.fields.get("value").unwrap(), &serde_json::json!("1000"));
1062    }
1063
1064    #[test]
1065    fn test_move_value_null() {
1066        let val = MoveValue::Null;
1067        assert!(val.as_bool().is_none());
1068        assert!(val.as_u64().is_none());
1069        assert!(val.as_str().is_none());
1070        assert!(val.as_address().is_none());
1071        assert!(val.as_vec().is_none());
1072    }
1073
1074    #[test]
1075    fn test_move_value_struct() {
1076        let mut fields = serde_json::Map::new();
1077        fields.insert("name".to_string(), serde_json::json!("test"));
1078        let s = MoveStruct { fields };
1079        let val = MoveValue::Struct(s);
1080
1081        // Struct doesn't have direct accessors
1082        if let MoveValue::Struct(inner) = val {
1083            assert!(inner.fields.contains_key("name"));
1084        } else {
1085            panic!("Expected Struct");
1086        }
1087    }
1088
1089    #[test]
1090    fn test_move_value_json_roundtrip() {
1091        // Test values that have unambiguous JSON representations
1092        let val = MoveValue::Bool(true);
1093        let json = serde_json::to_string(&val).unwrap();
1094        let deserialized: MoveValue = serde_json::from_str(&json).unwrap();
1095        assert_eq!(val, deserialized);
1096
1097        let val = MoveValue::Null;
1098        let json = serde_json::to_string(&val).unwrap();
1099        let deserialized: MoveValue = serde_json::from_str(&json).unwrap();
1100        assert_eq!(val, deserialized);
1101
1102        // Note: String and Number both serialize to JSON strings
1103        // and are both deserialized as the same variant based on serde's untagged enum logic
1104        let val = MoveValue::Number("12345".to_string());
1105        let json = serde_json::to_string(&val).unwrap();
1106        assert_eq!(json, "\"12345\"");
1107
1108        let val = MoveValue::String("hello".to_string());
1109        let json = serde_json::to_string(&val).unwrap();
1110        assert_eq!(json, "\"hello\"");
1111    }
1112
1113    // Security tests for input length limits
1114
1115    #[test]
1116    fn test_identifier_length_limit() {
1117        // Valid length
1118        let valid = "a".repeat(128);
1119        assert!(Identifier::new(&valid).is_ok());
1120
1121        // Too long
1122        let too_long = "a".repeat(129);
1123        let result = Identifier::new(&too_long);
1124        assert!(result.is_err());
1125        assert!(result.unwrap_err().to_string().contains("too long"));
1126    }
1127
1128    #[test]
1129    fn test_type_tag_length_limit() {
1130        // Valid length
1131        let valid = format!("0x1::{}::Test", "a".repeat(100));
1132        assert!(TypeTag::from_str_strict(&valid).is_ok());
1133
1134        // Too long
1135        let too_long = format!("0x1::{}::Test", "a".repeat(2000));
1136        let result = TypeTag::from_str_strict(&too_long);
1137        assert!(result.is_err());
1138        assert!(result.unwrap_err().to_string().contains("too long"));
1139    }
1140
1141    #[test]
1142    fn test_type_tag_nesting_depth_limit() {
1143        // Valid nesting depth
1144        let valid = "vector<vector<vector<u8>>>";
1145        assert!(TypeTag::from_str_strict(valid).is_ok());
1146
1147        // Too deeply nested (9 levels)
1148        let too_deep = "vector<vector<vector<vector<vector<vector<vector<vector<vector<u8>>>>>>>>>";
1149        let result = TypeTag::from_str_strict(too_deep);
1150        assert!(result.is_err());
1151        assert!(result.unwrap_err().to_string().contains("too deep"));
1152    }
1153
1154    #[test]
1155    fn test_identifier_from_str() {
1156        // Test FromStr trait implementation for Identifier
1157        let id: Identifier = "my_function".parse().unwrap();
1158        assert_eq!(id.as_str(), "my_function");
1159
1160        // Invalid identifier via FromStr
1161        let result: Result<Identifier, _> = "123invalid".parse();
1162        assert!(result.is_err());
1163    }
1164
1165    #[test]
1166    fn test_module_id_from_str() {
1167        // Test FromStr trait implementation for MoveModuleId
1168        let module: MoveModuleId = "0x1::coin".parse().unwrap();
1169        assert_eq!(module.address, AccountAddress::ONE);
1170        assert_eq!(module.name.as_str(), "coin");
1171
1172        // Invalid module via FromStr
1173        let result: Result<MoveModuleId, _> = "invalid".parse();
1174        assert!(result.is_err());
1175    }
1176
1177    #[test]
1178    fn test_struct_tag_simple() {
1179        // Test StructTag::simple constructor
1180        let tag = StructTag::simple(AccountAddress::ONE, "coin", "CoinStore").unwrap();
1181        assert_eq!(tag.address, AccountAddress::ONE);
1182        assert_eq!(tag.module.as_str(), "coin");
1183        assert_eq!(tag.name.as_str(), "CoinStore");
1184        assert!(tag.type_args.is_empty());
1185
1186        // Invalid module name
1187        let result = StructTag::simple(AccountAddress::ONE, "123invalid", "CoinStore");
1188        assert!(result.is_err());
1189
1190        // Invalid struct name
1191        let result = StructTag::simple(AccountAddress::ONE, "coin", "123invalid");
1192        assert!(result.is_err());
1193    }
1194
1195    #[test]
1196    fn test_struct_tag_display_with_multiple_type_args() {
1197        // Test Display with multiple type args (exercises line 224 comma separator)
1198        let tag = StructTag::new(
1199            AccountAddress::ONE,
1200            Identifier::new("table").unwrap(),
1201            Identifier::new("Table").unwrap(),
1202            vec![TypeTag::Address, TypeTag::U64, TypeTag::Bool],
1203        );
1204        let display = tag.to_string();
1205        assert_eq!(display, "0x1::table::Table<address, u64, bool>");
1206    }
1207
1208    #[test]
1209    fn test_type_tag_signed_integers_display() {
1210        // Test Display for signed integer types (i8, i16, i32, i64, i128, i256)
1211        assert_eq!(TypeTag::I8.to_string(), "i8");
1212        assert_eq!(TypeTag::I16.to_string(), "i16");
1213        assert_eq!(TypeTag::I32.to_string(), "i32");
1214        assert_eq!(TypeTag::I64.to_string(), "i64");
1215        assert_eq!(TypeTag::I128.to_string(), "i128");
1216        assert_eq!(TypeTag::I256.to_string(), "i256");
1217    }
1218
1219    #[test]
1220    fn test_move_value_as_u128_comprehensive() {
1221        // Test as_u128 method with max value
1222        let val = MoveValue::Number("340282366920938463463374607431768211455".to_string()); // max u128
1223        assert_eq!(val.as_u128(), Some(u128::MAX));
1224
1225        let val = MoveValue::Number("0".to_string());
1226        assert_eq!(val.as_u128(), Some(0));
1227
1228        // Non-number returns None
1229        let val = MoveValue::Bool(true);
1230        assert_eq!(val.as_u128(), None);
1231
1232        // Invalid number string returns None
1233        let val = MoveValue::Number("not_a_number".to_string());
1234        assert_eq!(val.as_u128(), None);
1235    }
1236
1237    #[test]
1238    fn test_type_tag_parse_empty_type_args() {
1239        // Struct with no type args
1240        let tag = TypeTag::from_str_strict("0x1::coin::CoinInfo").unwrap();
1241        if let TypeTag::Struct(s) = tag {
1242            assert!(s.type_args.is_empty());
1243        } else {
1244            panic!("Expected Struct");
1245        }
1246    }
1247
1248    #[test]
1249    fn test_type_tag_rejects_empty_type_args() {
1250        // Empty generic list.
1251        assert!(TypeTag::from_str_strict("0x1::a::B<>").is_err());
1252        // Empty entry between commas.
1253        assert!(TypeTag::from_str_strict("0x1::a::B<,>").is_err());
1254        assert!(TypeTag::from_str_strict("0x1::a::B<u8,,u16>").is_err());
1255        // Leading comma.
1256        assert!(TypeTag::from_str_strict("0x1::a::B<,u8>").is_err());
1257        // Trailing comma.
1258        assert!(TypeTag::from_str_strict("0x1::a::B<u8,>").is_err());
1259        // Empty vector element.
1260        assert!(TypeTag::from_str_strict("vector<>").is_err());
1261    }
1262
1263    #[test]
1264    fn test_type_tag_whitespace_consistency() {
1265        // Whitespace inside generics and vectors is trimmed consistently.
1266        let a = TypeTag::from_str_strict("0x1::a::B< u8 >").unwrap();
1267        let b = TypeTag::from_str_strict("0x1::a::B<u8>").unwrap();
1268        assert_eq!(a, b);
1269
1270        let v1 = TypeTag::from_str_strict("vector< u8 >").unwrap();
1271        let v2 = TypeTag::from_str_strict("vector<u8>").unwrap();
1272        assert_eq!(v1, v2);
1273        assert_eq!(v1, TypeTag::vector(TypeTag::U8));
1274
1275        // Whitespace around multiple args.
1276        let t = TypeTag::from_str_strict("0x1::table::Table< address , u64 >").unwrap();
1277        if let TypeTag::Struct(s) = t {
1278            assert_eq!(s.type_args, vec![TypeTag::Address, TypeTag::U64]);
1279        } else {
1280            panic!("expected struct");
1281        }
1282    }
1283
1284    #[test]
1285    fn test_type_tag_parse_nested_generics() {
1286        // Tests bracket tracking with nested generics (lines 448-449)
1287        let tag = TypeTag::from_str_strict(
1288            "0x1::table::Table<0x1::string::String, vector<0x1::aptos_coin::AptosCoin>>",
1289        )
1290        .unwrap();
1291        if let TypeTag::Struct(s) = tag {
1292            assert_eq!(s.name.as_str(), "Table");
1293            assert_eq!(s.type_args.len(), 2);
1294            // First arg is a struct
1295            assert!(matches!(s.type_args[0], TypeTag::Struct(_)));
1296            // Second arg is a vector
1297            assert!(matches!(s.type_args[1], TypeTag::Vector(_)));
1298        } else {
1299            panic!("Expected Struct");
1300        }
1301    }
1302}