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