iota_types/
type_input.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt::{Display, Formatter};
6
7use anyhow::Result;
8use iota_macros::EnumVariantOrder;
9use move_core_types::{
10    account_address::AccountAddress,
11    identifier::Identifier,
12    language_storage::{StructTag, TypeTag},
13};
14use serde::{Deserialize, Serialize};
15
16use crate::parse_iota_type_tag;
17
18#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
19pub struct TypeName {
20    /// String representation of the type. All types are represented
21    /// using their source syntax:
22    /// "u8", "u64", "bool", "address", "vector", and so on for primitive types.
23    /// Struct types are represented as fully qualified type names; e.g.
24    /// `00000000000000000000000000000001::string::String` or
25    /// `0000000000000000000000000000000a::module_name1::type_name1<0000000000000000000000000000000a::module_name2::type_name2<u64>>`
26    /// Addresses are hex-encoded lowercase values of length ADDRESS_LENGTH (16,
27    /// 20, or 32 depending on the Move platform)
28    pub name: String,
29}
30
31impl From<&TypeInput> for TypeName {
32    fn from(value: &TypeInput) -> Self {
33        TypeName {
34            name: value.to_canonical_string(false /* with_prefix */),
35        }
36    }
37}
38
39impl From<&TypeTag> for TypeName {
40    fn from(value: &TypeTag) -> Self {
41        TypeName {
42            name: value.to_canonical_string(false /* with_prefix */),
43        }
44    }
45}
46
47impl TryFrom<TypeName> for TypeInput {
48    type Error = anyhow::Error;
49
50    fn try_from(value: TypeName) -> Result<Self, Self::Error> {
51        parse_iota_type_tag(&value.name).map(|tag| tag.into())
52    }
53}
54
55#[derive(Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)]
56pub struct StructInput {
57    pub address: AccountAddress,
58    pub module: String,
59    pub name: String,
60    // alias for compatibility with old json serialized data.
61    #[serde(rename = "type_args", alias = "type_params")]
62    pub type_params: Vec<TypeInput>,
63}
64
65#[derive(
66    Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord, EnumVariantOrder,
67)]
68pub enum TypeInput {
69    // alias for compatibility with old json serialized data.
70    #[serde(rename = "bool", alias = "Bool")]
71    Bool,
72    #[serde(rename = "u8", alias = "U8")]
73    U8,
74    #[serde(rename = "u64", alias = "U64")]
75    U64,
76    #[serde(rename = "u128", alias = "U128")]
77    U128,
78    #[serde(rename = "address", alias = "Address")]
79    Address,
80    #[serde(rename = "signer", alias = "Signer")]
81    Signer,
82    #[serde(rename = "vector", alias = "Vector")]
83    Vector(Box<TypeInput>),
84    #[serde(rename = "struct", alias = "Struct")]
85    Struct(Box<StructInput>),
86
87    // NOTE: Added in bytecode version v6, do not reorder!
88    #[serde(rename = "u16", alias = "U16")]
89    U16,
90    #[serde(rename = "u32", alias = "U32")]
91    U32,
92    #[serde(rename = "u256", alias = "U256")]
93    U256,
94}
95
96impl TypeInput {
97    /// Return a canonical string representation of the type. All types are
98    /// represented using their source syntax:
99    ///
100    /// - "bool", "u8", "u16", "u32", "u64", "u128", "u256", "address",
101    ///   "signer", "vector" for ground types.
102    ///
103    /// - Structs are represented as fully qualified type names, with or without
104    ///   the prefix "0x" depending on the `with_prefix` flag, e.g.
105    ///   `0x000...0001::string::String` or
106    ///   `0x000...000a::m::T<0x000...000a::n::U<u64>>`.
107    ///
108    /// - Addresses are hex-encoded lowercase values of length 32 (zero-padded).
109    ///
110    /// Note: this function is guaranteed to be stable -- suitable for use
111    /// inside Move native functions or the VM. By contrast, this type's
112    /// `Display` implementation is subject to change and should be used
113    /// inside code that needs to return a stable output (e.g. that might be
114    /// committed to effects on-chain).
115    pub fn to_canonical_string(&self, with_prefix: bool) -> String {
116        self.to_canonical_display(with_prefix).to_string()
117    }
118
119    /// Return the canonical string representation of the TypeTag conditionally
120    /// with prefix 0x
121    pub fn to_canonical_display(&self, with_prefix: bool) -> impl std::fmt::Display + '_ {
122        struct CanonicalDisplay<'a> {
123            data: &'a TypeInput,
124            with_prefix: bool,
125        }
126
127        impl std::fmt::Display for CanonicalDisplay<'_> {
128            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129                match self.data {
130                    TypeInput::Bool => write!(f, "bool"),
131                    TypeInput::U8 => write!(f, "u8"),
132                    TypeInput::U16 => write!(f, "u16"),
133                    TypeInput::U32 => write!(f, "u32"),
134                    TypeInput::U64 => write!(f, "u64"),
135                    TypeInput::U128 => write!(f, "u128"),
136                    TypeInput::U256 => write!(f, "u256"),
137                    TypeInput::Address => write!(f, "address"),
138                    TypeInput::Signer => write!(f, "signer"),
139                    TypeInput::Vector(t) => {
140                        write!(f, "vector<{}>", t.to_canonical_display(self.with_prefix))
141                    }
142                    TypeInput::Struct(s) => {
143                        write!(f, "{}", s.to_canonical_display(self.with_prefix))
144                    }
145                }
146            }
147        }
148
149        CanonicalDisplay {
150            data: self,
151            with_prefix,
152        }
153    }
154
155    /// Convert the TypeInput into a TypeTag without checking for validity of
156    /// identifiers within the StructTag. DO NOT USE UNLESS YOU KNOW WHAT
157    /// YOU ARE DOING AND WHY THIS IS SAFE TO CALL.
158    ///
159    /// # Safety
160    ///
161    /// Preserving existing behaviour for identifier deserialization within type
162    /// tags and inputs.
163    pub unsafe fn into_type_tag_unchecked(self) -> TypeTag {
164        match self {
165            TypeInput::Bool => TypeTag::Bool,
166            TypeInput::U8 => TypeTag::U8,
167            TypeInput::U16 => TypeTag::U16,
168            TypeInput::U32 => TypeTag::U32,
169            TypeInput::U64 => TypeTag::U64,
170            TypeInput::U128 => TypeTag::U128,
171            TypeInput::U256 => TypeTag::U256,
172            TypeInput::Address => TypeTag::Address,
173            TypeInput::Signer => TypeTag::Signer,
174            TypeInput::Vector(inner) => TypeTag::Vector(Box::new(inner.into_type_tag_unchecked())),
175            TypeInput::Struct(inner) => {
176                let StructInput {
177                    address,
178                    module,
179                    name,
180                    type_params,
181                } = *inner;
182                TypeTag::Struct(Box::new(StructTag {
183                    address,
184                    module: Identifier::new_unchecked(module),
185                    name: Identifier::new_unchecked(name),
186                    type_params: type_params
187                        .into_iter()
188                        .map(|ty| ty.into_type_tag_unchecked())
189                        .collect(),
190                }))
191            }
192        }
193    }
194
195    /// Convert to a `TypeTag` consuming `self`. This can fail if this value
196    /// includes invalid identifiers.
197    pub fn into_type_tag(self) -> Result<TypeTag> {
198        self.as_type_tag()
199    }
200
201    /// Conversion to a `TypeTag`, which can fail if this value includes invalid
202    /// identifiers.
203    pub fn as_type_tag(&self) -> Result<TypeTag> {
204        // Keep this in sync with implementation in:
205        // crates/iota-package-resolver/src/lib.rs
206        use TypeInput as I;
207        use TypeTag as T;
208        Ok(match self {
209            I::Bool => T::Bool,
210            I::U8 => T::U8,
211            I::U16 => T::U16,
212            I::U32 => T::U32,
213            I::U64 => T::U64,
214            I::U128 => T::U128,
215            I::U256 => T::U256,
216            I::Address => T::Address,
217            I::Signer => T::Signer,
218            I::Vector(t) => T::Vector(Box::new(t.as_type_tag()?)),
219            I::Struct(s) => {
220                let StructInput {
221                    address,
222                    module,
223                    name,
224                    type_params,
225                } = s.as_ref();
226                let type_params = type_params
227                    .iter()
228                    .map(|t| t.as_type_tag())
229                    .collect::<Result<_>>()?;
230                T::Struct(Box::new(StructTag {
231                    address: *address,
232                    module: Identifier::new(module.to_owned())?,
233                    name: Identifier::new(name.to_owned())?,
234                    type_params,
235                }))
236            }
237        })
238    }
239}
240
241impl StructInput {
242    /// Return a canonical string representation of the struct.
243    ///
244    /// - Structs are represented as fully qualified type names, with or without
245    ///   the prefix "0x" depending on the `with_prefix` flag, e.g.
246    ///   `0x000...0001::string::String` or
247    ///   `0x000...000a::m::T<0x000...000a::n::U<u64>>`.
248    ///
249    /// - Addresses are hex-encoded lowercase values of length 32 (zero-padded).
250    ///
251    /// Note: this function is guaranteed to be stable -- suitable for use
252    /// inside Move native functions or the VM. By contrast, this type's
253    /// `Display` implementation is subject to change and should be used
254    /// inside code that needs to return a stable output (e.g. that might be
255    /// committed to effects on-chain).
256    pub fn to_canonical_string(&self, with_prefix: bool) -> String {
257        self.to_canonical_display(with_prefix).to_string()
258    }
259
260    /// Implements the canonical string representation of the StructTag with the
261    /// prefix 0x
262    pub fn to_canonical_display(&self, with_prefix: bool) -> impl std::fmt::Display + '_ {
263        struct CanonicalDisplay<'a> {
264            data: &'a StructInput,
265            with_prefix: bool,
266        }
267
268        impl std::fmt::Display for CanonicalDisplay<'_> {
269            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270                write!(
271                    f,
272                    "{}::{}::{}",
273                    self.data.address.to_canonical_display(self.with_prefix),
274                    self.data.module,
275                    self.data.name
276                )?;
277
278                if let Some(first_ty) = self.data.type_params.first() {
279                    write!(f, "<")?;
280                    write!(f, "{}", first_ty.to_canonical_display(self.with_prefix))?;
281                    for ty in self.data.type_params.iter().skip(1) {
282                        // Note that unlike Display for StructTag, there is no space between the
283                        // comma and canonical display. This follows the
284                        // original to_canonical_string() implementation.
285                        write!(f, ",{}", ty.to_canonical_display(self.with_prefix))?;
286                    }
287                    write!(f, ">")?;
288                }
289                Ok(())
290            }
291        }
292
293        CanonicalDisplay {
294            data: self,
295            with_prefix,
296        }
297    }
298}
299
300impl From<TypeTag> for TypeInput {
301    fn from(tag: TypeTag) -> Self {
302        match tag {
303            TypeTag::Bool => TypeInput::Bool,
304            TypeTag::U8 => TypeInput::U8,
305            TypeTag::U64 => TypeInput::U64,
306            TypeTag::U128 => TypeInput::U128,
307            TypeTag::Address => TypeInput::Address,
308            TypeTag::Signer => TypeInput::Signer,
309            TypeTag::Vector(inner) => TypeInput::Vector(Box::new(TypeInput::from(*inner))),
310            TypeTag::Struct(inner) => TypeInput::Struct(Box::new(StructInput::from(*inner))),
311            TypeTag::U16 => TypeInput::U16,
312            TypeTag::U32 => TypeInput::U32,
313            TypeTag::U256 => TypeInput::U256,
314        }
315    }
316}
317
318impl From<StructTag> for StructInput {
319    fn from(tag: StructTag) -> Self {
320        StructInput {
321            address: tag.address,
322            module: tag.module.to_string(),
323            name: tag.name.to_string(),
324            type_params: tag.type_params.into_iter().map(TypeInput::from).collect(),
325        }
326    }
327}
328
329impl From<StructInput> for TypeInput {
330    fn from(t: StructInput) -> TypeInput {
331        TypeInput::Struct(Box::new(t))
332    }
333}
334
335impl Display for StructInput {
336    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
337        write!(
338            f,
339            "0x{}::{}::{}",
340            self.address.short_str_lossless(),
341            self.module,
342            self.name
343        )?;
344
345        let mut prefix = "<";
346        for ty in &self.type_params {
347            write!(f, "{prefix}{ty}")?;
348            prefix = ", ";
349        }
350        if !self.type_params.is_empty() {
351            write!(f, ">")?;
352        }
353
354        Ok(())
355    }
356}
357
358impl Display for TypeInput {
359    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
360        match self {
361            TypeInput::Struct(s) => write!(f, "{s}"),
362            TypeInput::Vector(ty) => write!(f, "vector<{ty}>"),
363            TypeInput::U8 => write!(f, "u8"),
364            TypeInput::U16 => write!(f, "u16"),
365            TypeInput::U32 => write!(f, "u32"),
366            TypeInput::U64 => write!(f, "u64"),
367            TypeInput::U128 => write!(f, "u128"),
368            TypeInput::U256 => write!(f, "u256"),
369            TypeInput::Address => write!(f, "address"),
370            TypeInput::Signer => write!(f, "signer"),
371            TypeInput::Bool => write!(f, "bool"),
372        }
373    }
374}
375
376#[cfg(test)]
377mod test {
378    use iota_enum_compat_util::check_enum_compat_order;
379
380    use super::TypeInput;
381
382    #[test]
383    fn enforce_order_test() {
384        let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
385        path.extend(["tests", "staged", "type_input.yaml"]);
386        check_enum_compat_order::<TypeInput>(path);
387    }
388}