af_move_type/
lib.rs

1#![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))]
2
3//! Defines the core standard for representing Move types off-chain and their type tags.
4//!
5//! The core items are [`MoveType`](crate::MoveType) and [`MoveTypeTag`](crate::MoveTypeTag). These
6//! are useful trait bounds to use when dealing with generic off-chain Move type representations.
7//! They are implemented for the primitive types that correspond to Move's primitives
8//! (integers/bool). Also included is [`MoveVec`](crate::vector::MoveVec), corresponding to `vector`
9//! and defining a pretty [`Display`](::std::fmt::Display).
10//!
11//! For Move structs (objects), [`MoveStruct`](crate::MoveStruct) should be used as it has an
12//! associated [`MoveStructTag`](crate::MoveStructTag). The
13//! [`MoveStruct`](af_move_type_derive::MoveStruct) derive macro is exported for automatically
14//! creating a `MoveStructTag` implementation from normal Rust struct declarations.
15//!
16//! A specific instance of a Move type is represented by [`MoveInstance`](crate::MoveInstance).
17use std::fmt::Debug;
18use std::hash::Hash;
19use std::str::FromStr;
20
21pub use af_move_type_derive::MoveStruct;
22use af_sui_types::u256::U256;
23use af_sui_types::{Address, IdentStr, Identifier, ObjectId, StructTag, TypeTag};
24use serde::{Deserialize, Serialize};
25
26#[doc(hidden)]
27pub mod external;
28pub mod otw;
29pub mod vector;
30
31// =============================================================================
32//  Errors
33// =============================================================================
34
35#[derive(thiserror::Error, Debug)]
36pub enum TypeTagError {
37    #[error("Wrong TypeTag variant: expected {expected}, got {got}")]
38    Variant { expected: String, got: TypeTag },
39    #[error("StructTag params: {0}")]
40    StructTag(#[from] StructTagError),
41}
42
43#[derive(thiserror::Error, Debug)]
44pub enum StructTagError {
45    #[error("Wrong address: expected {expected}, got {got}")]
46    Address { expected: Address, got: Address },
47    #[error("Wrong module: expected {expected}, got {got}")]
48    Module {
49        expected: Identifier,
50        got: Identifier,
51    },
52    #[error("Wrong name: expected {expected}, got {got}")]
53    Name {
54        expected: Identifier,
55        got: Identifier,
56    },
57    #[error("Wrong type parameters: {0}")]
58    TypeParams(#[from] TypeParamsError),
59}
60
61#[derive(thiserror::Error, Debug)]
62pub enum TypeParamsError {
63    #[error("Wrong number of generics: expected {expected}, got {got}")]
64    Number { expected: usize, got: usize },
65    #[error("Wrong type for generic: {0}")]
66    TypeTag(Box<TypeTagError>),
67}
68
69impl From<TypeTagError> for TypeParamsError {
70    fn from(value: TypeTagError) -> Self {
71        Self::TypeTag(Box::new(value))
72    }
73}
74
75#[derive(thiserror::Error, Debug)]
76pub enum ParseTypeTagError {
77    #[error("Parsing TypeTag: {0}")]
78    FromStr(#[from] sui_sdk_types::TypeParseError),
79    #[error("Converting from TypeTag: {0}")]
80    TypeTag(#[from] TypeTagError),
81}
82
83#[derive(thiserror::Error, Debug)]
84pub enum ParseStructTagError {
85    #[error("Parsing StructTag: {0}")]
86    FromStr(#[from] sui_sdk_types::TypeParseError),
87    #[error("Converting from StructTag: {0}")]
88    StructTag(#[from] StructTagError),
89}
90
91#[derive(thiserror::Error, Debug)]
92pub enum FromRawTypeError {
93    #[error("Converting from TypeTag: {0}")]
94    TypeTag(#[from] TypeTagError),
95    #[error("Deserializing BCS: {0}")]
96    Bcs(#[from] bcs::Error),
97}
98
99#[derive(thiserror::Error, Debug)]
100pub enum FromRawStructError {
101    #[error("Converting from StructTag: {0}")]
102    StructTag(#[from] StructTagError),
103    #[error("Deserializing BCS: {0}")]
104    Bcs(#[from] bcs::Error),
105}
106
107// =============================================================================
108//  MoveType
109// =============================================================================
110
111/// Trait marking a Move data type. Has a specific way to construct a `TypeTag`.
112pub trait MoveType:
113    Clone
114    + std::fmt::Debug
115    + std::fmt::Display
116    + for<'de> Deserialize<'de>
117    + Serialize
118    + PartialEq
119    + Eq
120    + std::hash::Hash
121{
122    type TypeTag: MoveTypeTag;
123
124    /// Deserialize the contents of the Move type from BCS bytes.
125    fn from_bcs(bytes: &[u8]) -> bcs::Result<Self> {
126        bcs::from_bytes(bytes)
127    }
128
129    /// Consuming version of [`to_bcs`](MoveType::to_bcs).
130    fn into_bcs(self) -> bcs::Result<Vec<u8>> {
131        bcs::to_bytes(&self)
132    }
133
134    /// Serialize the contents of the Move type to BCS bytes.
135    fn to_bcs(&self) -> bcs::Result<Vec<u8>> {
136        bcs::to_bytes(self)
137    }
138
139    /// Consuming version of [`to_json`](MoveType::to_json).
140    fn into_json(self) -> serde_json::Value {
141        let mut value = serde_json::json!(self);
142        // Move only uses integer values, for which the JSON encoding uses strings
143        number_to_string_value_recursive(&mut value);
144        value
145    }
146
147    /// Serialize the contents of the Move type to JSON.
148    ///
149    /// The method takes care to use JSON [`String`](serde_json::Value::String) representations for
150    /// integer types, for which [`serde`] would use [`Number`](serde_json::Value::Number).
151    ///
152    /// This is useful for interacting with the RPC.
153    fn to_json(&self) -> serde_json::Value {
154        let mut value = serde_json::json!(self);
155        // Move only uses integer values, for which the JSON encoding uses strings
156        number_to_string_value_recursive(&mut value);
157        value
158    }
159}
160
161pub trait MoveTypeTag:
162    Into<TypeTag>
163    + TryFrom<TypeTag, Error = TypeTagError>
164    + FromStr
165    + Clone
166    + Debug
167    + PartialEq
168    + Eq
169    + Hash
170    + for<'de> Deserialize<'de>
171    + PartialOrd
172    + Ord
173    + Serialize
174{
175}
176
177impl<T> MoveTypeTag for T where
178    T: Into<TypeTag>
179        + TryFrom<TypeTag, Error = TypeTagError>
180        + FromStr
181        + Clone
182        + Debug
183        + PartialEq
184        + Eq
185        + Hash
186        + for<'de> Deserialize<'de>
187        + PartialOrd
188        + Ord
189        + Serialize
190{
191}
192
193// =============================================================================
194//  MoveStruct
195// =============================================================================
196
197/// Trait marking a Move struct type. Has a specific way to construct a `StructTag`.
198pub trait MoveStruct: MoveType<TypeTag = Self::StructTag> {
199    type StructTag: MoveStructTag;
200}
201
202pub trait MoveStructTag:
203    Into<StructTag> + TryFrom<StructTag, Error = StructTagError> + MoveTypeTag
204{
205}
206
207impl<T> MoveStructTag for T where
208    T: Into<StructTag> + TryFrom<StructTag, Error = StructTagError> + MoveTypeTag
209{
210}
211
212// =============================================================================
213//  Abilities
214// =============================================================================
215
216pub trait HasKey: MoveStruct {
217    fn object_id(&self) -> ObjectId;
218}
219
220pub trait HasCopy: MoveStruct + Copy {}
221
222pub trait HasStore: MoveStruct {}
223
224pub trait HasDrop: MoveStruct {}
225
226// =============================================================================
227//  Static attributes
228// =============================================================================
229
230/// Move type for which the type tag can be derived at compile time.
231pub trait StaticTypeTag: MoveType {
232    fn type_() -> Self::TypeTag;
233
234    fn type_tag() -> TypeTag {
235        Self::type_().into()
236    }
237}
238
239/// Move struct for which the address of the package is known at compile time.
240pub trait StaticAddress: MoveStruct {
241    fn address() -> Address;
242}
243
244/// Move struct for which the module in the package is known at compile time.
245pub trait StaticModule: MoveStruct {
246    fn module() -> Identifier;
247}
248
249/// Move struct for which the name of object is known at compile time.
250pub trait StaticName: MoveStruct {
251    fn name() -> Identifier;
252}
253
254/// Move struct for which the type args of object are known at compile time.
255pub trait StaticTypeParams: MoveStruct {
256    fn type_params() -> Vec<TypeTag>;
257}
258
259/// Move struct for which the struct tag can be derived at compile time.
260pub trait StaticStructTag: MoveStruct {
261    fn struct_tag() -> StructTag;
262}
263
264impl<T> StaticStructTag for T
265where
266    T: StaticAddress + StaticModule + StaticName + StaticTypeParams,
267{
268    fn struct_tag() -> StructTag {
269        StructTag {
270            address: Self::address(),
271            module: Self::module(),
272            name: Self::name(),
273            type_params: Self::type_params(),
274        }
275    }
276}
277
278// =============================================================================
279//  MoveInstance
280// =============================================================================
281
282/// Represents an instance of a Move type.
283///
284/// Both `type_` and `value` are necessary to represent an instance since otherwise there would be
285/// ambiguity, e.g., when the same package is published twice on-chain.
286#[derive(Clone, Debug, PartialEq, Eq, Hash)]
287pub struct MoveInstance<T: MoveType> {
288    pub type_: T::TypeTag,
289    pub value: T,
290}
291
292impl<T: StaticTypeTag> From<T> for MoveInstance<T> {
293    fn from(value: T) -> Self {
294        Self {
295            type_: T::type_(),
296            value,
297        }
298    }
299}
300
301impl<T: MoveStruct + tabled::Tabled> std::fmt::Display for MoveInstance<T> {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        use tabled::Table;
304        use tabled::settings::panel::Header;
305        use tabled::settings::{Rotate, Settings, Style};
306
307        let stag: StructTag = self.type_.clone().into();
308        let settings = Settings::default()
309            .with(Rotate::Left)
310            .with(Rotate::Top)
311            .with(Style::rounded())
312            .with(Header::new(stag.to_string()));
313        let mut table = Table::new([&self.value]);
314        table.with(settings);
315        write!(f, "{table}")
316    }
317}
318
319impl std::fmt::Display for MoveInstance<Address> {
320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321        write!(f, "{}", self.value)
322    }
323}
324
325macro_rules! impl_primitive_move_instance_display {
326    ($($type:ty)+) => {$(
327        impl std::fmt::Display for MoveInstance<$type> {
328            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329                write!(f, "{}", self.value)
330            }
331        }
332    )+};
333}
334
335impl_primitive_move_instance_display! {
336    bool
337    u8
338    u16
339    u32
340    u64
341    u128
342    U256
343}
344
345impl<T: MoveType> MoveInstance<T> {
346    /// Convenience function for constructing from raw RPC-returned general types.
347    pub fn from_raw_type(tag: TypeTag, bytes: &[u8]) -> Result<Self, FromRawTypeError> {
348        Ok(Self {
349            type_: tag.try_into()?,
350            value: T::from_bcs(bytes)?,
351        })
352    }
353}
354
355impl<T: MoveStruct> MoveInstance<T> {
356    /// Convenience function for constructing from raw RPC-returned structs.
357    pub fn from_raw_struct(stag: StructTag, bytes: &[u8]) -> Result<Self, FromRawStructError> {
358        Ok(Self {
359            type_: stag.try_into()?,
360            value: T::from_bcs(bytes)?,
361        })
362    }
363}
364
365fn number_to_string_value_recursive(value: &mut serde_json::Value) {
366    match value {
367        serde_json::Value::Array(a) => {
368            for v in a {
369                number_to_string_value_recursive(v)
370            }
371        }
372        serde_json::Value::Number(n) => *value = serde_json::Value::String(n.to_string()),
373        serde_json::Value::Object(o) => {
374            for v in o.values_mut() {
375                number_to_string_value_recursive(v)
376            }
377        }
378        _ => (),
379    }
380}
381
382/// Error for [`ObjectExt`].
383#[derive(thiserror::Error, Debug)]
384pub enum ObjectError {
385    #[error("Object is not a Move struct")]
386    NotStruct,
387    #[error(transparent)]
388    FromRawStruct(#[from] FromRawStructError),
389}
390
391/// Extract and parse a [`MoveStruct`] from a Sui object.
392pub trait ObjectExt {
393    /// Extract and parse a [`MoveStruct`] from a Sui object.
394    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError>;
395}
396
397impl ObjectExt for af_sui_types::Object {
398    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError> {
399        let _struct = self.as_move().ok_or(ObjectError::NotStruct)?;
400        MoveInstance::from_raw_struct(_struct.type_.clone().into(), &_struct.contents)
401            .map_err(From::from)
402    }
403}
404
405impl ObjectExt for sui_sdk_types::Object {
406    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError> {
407        let sui_sdk_types::ObjectData::Struct(s) = self.data() else {
408            return Err(ObjectError::NotStruct);
409        };
410        MoveInstance::from_raw_struct(s.object_type().clone(), s.contents()).map_err(From::from)
411    }
412}
413
414// =============================================================================
415// Trait impls
416// =============================================================================
417
418macro_rules! impl_primitive_type_tags {
419    ($($typ:ty: ($type_:ident, $variant:ident)),*) => {
420        $(
421            #[derive(
422                Clone,
423                Debug,
424                PartialEq,
425                Eq,
426                Hash,
427                Deserialize,
428                PartialOrd,
429                Ord,
430                Serialize
431            )]
432            pub struct $type_;
433
434            impl From<$type_> for TypeTag {
435                fn from(_value: $type_) -> Self {
436                    Self::$variant
437                }
438            }
439
440            impl TryFrom<TypeTag> for $type_ {
441                type Error = TypeTagError;
442
443                fn try_from(value: TypeTag) -> Result<Self, Self::Error> {
444                    match value {
445                        TypeTag::$variant => Ok(Self),
446                        _ => Err(TypeTagError::Variant {
447                            expected: stringify!($variant).to_owned(),
448                            got: value }
449                        )
450                    }
451                }
452            }
453
454            impl FromStr for $type_ {
455                type Err = ParseTypeTagError;
456
457                fn from_str(s: &str) -> Result<Self, Self::Err> {
458                    let tag: TypeTag = s.parse()?;
459                    Ok(tag.try_into()?)
460                }
461            }
462
463            impl MoveType for $typ {
464                type TypeTag = $type_;
465            }
466
467            impl StaticTypeTag for $typ {
468                fn type_() -> Self::TypeTag {
469                    $type_ {}
470                }
471            }
472        )*
473    };
474}
475
476impl_primitive_type_tags! {
477    Address: (AddressTypeTag, Address),
478    bool: (BoolTypeTag, Bool),
479    u8: (U8TypeTag, U8),
480    u16: (U16TypeTag, U16),
481    u32: (U32TypeTag, U32),
482    u64: (U64TypeTag, U64),
483    u128: (U128TypeTag, U128),
484    U256: (U256TypeTag, U256)
485}
486
487#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, PartialOrd, Ord, Serialize)]
488pub struct StringTypeTag;
489
490impl From<StringTypeTag> for TypeTag {
491    fn from(value: StringTypeTag) -> Self {
492        Self::Struct(Box::new(value.into()))
493    }
494}
495
496impl TryFrom<TypeTag> for StringTypeTag {
497    type Error = TypeTagError;
498
499    fn try_from(value: TypeTag) -> Result<Self, Self::Error> {
500        match value {
501            TypeTag::Struct(stag) => Ok((*stag).try_into()?),
502            other => Err(TypeTagError::Variant {
503                expected: "Struct(_)".to_owned(),
504                got: other,
505            }),
506        }
507    }
508}
509
510impl From<StringTypeTag> for StructTag {
511    fn from(_: StringTypeTag) -> Self {
512        String::struct_tag()
513    }
514}
515
516impl TryFrom<StructTag> for StringTypeTag {
517    type Error = StructTagError;
518
519    fn try_from(value: StructTag) -> Result<Self, Self::Error> {
520        use StructTagError::*;
521        let StructTag {
522            address,
523            module,
524            name,
525            type_params,
526        } = value;
527        let expected = String::struct_tag();
528        if address != expected.address {
529            return Err(Address {
530                expected: expected.address,
531                got: address,
532            });
533        }
534        if module != expected.module {
535            return Err(Module {
536                expected: expected.module,
537                got: module,
538            });
539        }
540        if name != expected.name {
541            return Err(Name {
542                expected: expected.name,
543                got: name,
544            });
545        }
546        if !type_params.is_empty() {
547            return Err(TypeParams(TypeParamsError::Number {
548                expected: 0,
549                got: type_params.len(),
550            }));
551        }
552        Ok(Self)
553    }
554}
555
556impl FromStr for StringTypeTag {
557    type Err = ParseStructTagError;
558
559    fn from_str(s: &str) -> Result<Self, Self::Err> {
560        let stag: StructTag = s.parse()?;
561        Ok(stag.try_into()?)
562    }
563}
564
565impl MoveType for String {
566    type TypeTag = StringTypeTag;
567}
568
569impl MoveStruct for String {
570    type StructTag = StringTypeTag;
571}
572
573impl StaticTypeTag for String {
574    fn type_() -> Self::TypeTag {
575        StringTypeTag {}
576    }
577}
578
579impl StaticAddress for String {
580    fn address() -> Address {
581        Address::new(af_sui_types::hex_address_bytes(b"0x1"))
582    }
583}
584
585impl StaticModule for String {
586    fn module() -> Identifier {
587        IdentStr::cast("string").to_owned()
588    }
589}
590
591impl StaticName for String {
592    fn name() -> Identifier {
593        IdentStr::cast("String").to_owned()
594    }
595}
596
597impl StaticTypeParams for String {
598    fn type_params() -> Vec<TypeTag> {
599        vec![]
600    }
601}