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, Identifier, ObjectId, StructTag, TypeTag};
24use serde::{Deserialize, Serialize};
25
26#[doc(hidden)]
27pub mod external;
28pub mod otw;
29mod primitives;
30mod string;
31pub mod vector;
32
33pub use self::primitives::{
34    AddressTypeTag,
35    BoolTypeTag,
36    U8TypeTag,
37    U16TypeTag,
38    U32TypeTag,
39    U64TypeTag,
40    U128TypeTag,
41    U256TypeTag,
42};
43pub use self::string::StringTypeTag;
44
45// =============================================================================
46//  MoveType
47// =============================================================================
48
49/// Trait marking a Move data type. Has a specific way to construct a `TypeTag`.
50pub trait MoveType:
51    Clone
52    + std::fmt::Debug
53    + std::fmt::Display
54    + for<'de> Deserialize<'de>
55    + Serialize
56    + PartialEq
57    + Eq
58    + std::hash::Hash
59{
60    type TypeTag: MoveTypeTag;
61
62    /// Deserialize the contents of the Move type from BCS bytes.
63    fn from_bcs(bytes: &[u8]) -> bcs::Result<Self> {
64        bcs::from_bytes(bytes)
65    }
66
67    /// Consuming version of [`to_bcs`](MoveType::to_bcs).
68    fn into_bcs(self) -> bcs::Result<Vec<u8>> {
69        bcs::to_bytes(&self)
70    }
71
72    /// Serialize the contents of the Move type to BCS bytes.
73    fn to_bcs(&self) -> bcs::Result<Vec<u8>> {
74        bcs::to_bytes(self)
75    }
76
77    /// Consuming version of [`to_json`](MoveType::to_json).
78    fn into_json(self) -> serde_json::Value {
79        let mut value = serde_json::json!(self);
80        // Move only uses integer values, for which the JSON encoding uses strings
81        number_to_string_value_recursive(&mut value);
82        value
83    }
84
85    /// Serialize the contents of the Move type to JSON.
86    ///
87    /// The method takes care to use JSON [`String`](serde_json::Value::String) representations for
88    /// integer types, for which [`serde`] would use [`Number`](serde_json::Value::Number).
89    ///
90    /// This is useful for interacting with the RPC.
91    fn to_json(&self) -> serde_json::Value {
92        let mut value = serde_json::json!(self);
93        // Move only uses integer values, for which the JSON encoding uses strings
94        number_to_string_value_recursive(&mut value);
95        value
96    }
97}
98
99pub trait MoveTypeTag:
100    Into<TypeTag>
101    + TryFrom<TypeTag, Error = TypeTagError>
102    + FromStr
103    + Clone
104    + Debug
105    + PartialEq
106    + Eq
107    + Hash
108    + for<'de> Deserialize<'de>
109    + PartialOrd
110    + Ord
111    + Serialize
112{
113}
114
115impl<T> MoveTypeTag for T where
116    T: Into<TypeTag>
117        + TryFrom<TypeTag, Error = TypeTagError>
118        + FromStr
119        + Clone
120        + Debug
121        + PartialEq
122        + Eq
123        + Hash
124        + for<'de> Deserialize<'de>
125        + PartialOrd
126        + Ord
127        + Serialize
128{
129}
130
131// =============================================================================
132//  MoveStruct
133// =============================================================================
134
135/// Trait marking a Move struct type. Has a specific way to construct a `StructTag`.
136pub trait MoveStruct: MoveType<TypeTag = Self::StructTag> {
137    type StructTag: MoveStructTag;
138}
139
140pub trait MoveStructTag:
141    Into<StructTag> + TryFrom<StructTag, Error = StructTagError> + MoveTypeTag
142{
143}
144
145impl<T> MoveStructTag for T where
146    T: Into<StructTag> + TryFrom<StructTag, Error = StructTagError> + MoveTypeTag
147{
148}
149
150// =============================================================================
151//  Abilities
152// =============================================================================
153
154pub trait HasKey: MoveStruct {
155    fn object_id(&self) -> ObjectId;
156}
157
158pub trait HasCopy: MoveStruct + Copy {}
159
160pub trait HasStore: MoveStruct {}
161
162pub trait HasDrop: MoveStruct {}
163
164// =============================================================================
165//  Static attributes
166// =============================================================================
167
168/// Move type for which the type tag can be derived at compile time.
169pub trait StaticTypeTag: MoveType {
170    fn type_() -> Self::TypeTag;
171
172    fn type_tag() -> TypeTag {
173        Self::type_().into()
174    }
175}
176
177/// Move struct for which the address of the package is known at compile time.
178pub trait StaticAddress: MoveStruct {
179    fn address() -> Address;
180}
181
182/// Move struct for which the module in the package is known at compile time.
183pub trait StaticModule: MoveStruct {
184    fn module() -> Identifier;
185}
186
187/// Move struct for which the name of object is known at compile time.
188pub trait StaticName: MoveStruct {
189    fn name() -> Identifier;
190}
191
192/// Move struct for which the type args of object are known at compile time.
193pub trait StaticTypeParams: MoveStruct {
194    fn type_params() -> Vec<TypeTag>;
195}
196
197/// Move struct for which the struct tag can be derived at compile time.
198pub trait StaticStructTag: MoveStruct {
199    fn struct_tag() -> StructTag;
200}
201
202impl<T> StaticStructTag for T
203where
204    T: StaticAddress + StaticModule + StaticName + StaticTypeParams,
205{
206    fn struct_tag() -> StructTag {
207        StructTag {
208            address: Self::address(),
209            module: Self::module(),
210            name: Self::name(),
211            type_params: Self::type_params(),
212        }
213    }
214}
215
216// =============================================================================
217//  MoveInstance
218// =============================================================================
219
220/// Represents an instance of a Move type.
221///
222/// Both `type_` and `value` are necessary to represent an instance since otherwise there would be
223/// ambiguity, e.g., when the same package is published twice on-chain.
224#[derive(Clone, Debug, PartialEq, Eq, Hash)]
225pub struct MoveInstance<T: MoveType> {
226    pub type_: T::TypeTag,
227    pub value: T,
228}
229
230impl<T: StaticTypeTag> From<T> for MoveInstance<T> {
231    fn from(value: T) -> Self {
232        Self {
233            type_: T::type_(),
234            value,
235        }
236    }
237}
238
239impl<T: MoveStruct + tabled::Tabled> std::fmt::Display for MoveInstance<T> {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        use tabled::Table;
242        use tabled::settings::panel::Header;
243        use tabled::settings::{Rotate, Settings, Style};
244
245        let stag: StructTag = self.type_.clone().into();
246        let settings = Settings::default()
247            .with(Rotate::Left)
248            .with(Rotate::Top)
249            .with(Style::rounded())
250            .with(Header::new(stag.to_string()));
251        let mut table = Table::new([&self.value]);
252        table.with(settings);
253        write!(f, "{table}")
254    }
255}
256
257impl std::fmt::Display for MoveInstance<Address> {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        write!(f, "{}", self.value)
260    }
261}
262
263macro_rules! impl_primitive_move_instance_display {
264    ($($type:ty)+) => {$(
265        impl std::fmt::Display for MoveInstance<$type> {
266            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267                write!(f, "{}", self.value)
268            }
269        }
270    )+};
271}
272
273impl_primitive_move_instance_display! {
274    bool
275    u8
276    u16
277    u32
278    u64
279    u128
280    U256
281}
282
283impl<T: MoveType> MoveInstance<T> {
284    /// Convenience function for constructing from raw RPC-returned general types.
285    pub fn from_raw_type(tag: TypeTag, bytes: &[u8]) -> Result<Self, FromRawTypeError> {
286        Ok(Self {
287            type_: tag.try_into()?,
288            value: T::from_bcs(bytes)?,
289        })
290    }
291}
292
293impl<T: MoveStruct> MoveInstance<T> {
294    /// Convenience function for constructing from raw RPC-returned structs.
295    pub fn from_raw_struct(stag: StructTag, bytes: &[u8]) -> Result<Self, FromRawStructError> {
296        Ok(Self {
297            type_: stag.try_into()?,
298            value: T::from_bcs(bytes)?,
299        })
300    }
301}
302
303fn number_to_string_value_recursive(value: &mut serde_json::Value) {
304    match value {
305        serde_json::Value::Array(a) => {
306            for v in a {
307                number_to_string_value_recursive(v)
308            }
309        }
310        serde_json::Value::Number(n) => *value = serde_json::Value::String(n.to_string()),
311        serde_json::Value::Object(o) => {
312            for v in o.values_mut() {
313                number_to_string_value_recursive(v)
314            }
315        }
316        _ => (),
317    }
318}
319
320// =============================================================================
321//  ObjectExt
322// =============================================================================
323
324/// Extract and parse a [`MoveStruct`] from a Sui object.
325pub trait ObjectExt {
326    /// Extract and parse a [`MoveStruct`] from a Sui object.
327    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError>;
328}
329
330impl ObjectExt for af_sui_types::Object {
331    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError> {
332        let _struct = self.as_move().ok_or(ObjectError::NotStruct)?;
333        MoveInstance::from_raw_struct(_struct.type_.clone().into(), &_struct.contents)
334            .map_err(From::from)
335    }
336}
337
338impl ObjectExt for sui_sdk_types::Object {
339    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError> {
340        let sui_sdk_types::ObjectData::Struct(s) = self.data() else {
341            return Err(ObjectError::NotStruct);
342        };
343        MoveInstance::from_raw_struct(s.object_type().clone(), s.contents()).map_err(From::from)
344    }
345}
346
347/// Error for [`ObjectExt`].
348#[derive(thiserror::Error, Debug)]
349pub enum ObjectError {
350    #[error("Object is not a Move struct")]
351    NotStruct,
352    #[error(transparent)]
353    FromRawStruct(#[from] FromRawStructError),
354}
355
356// =============================================================================
357//  Errors
358// =============================================================================
359
360#[derive(thiserror::Error, Debug)]
361pub enum TypeTagError {
362    #[error("Wrong TypeTag variant: expected {expected}, got {got}")]
363    Variant { expected: String, got: TypeTag },
364    #[error("StructTag params: {0}")]
365    StructTag(#[from] StructTagError),
366}
367
368#[derive(thiserror::Error, Debug)]
369pub enum StructTagError {
370    #[error("Wrong address: expected {expected}, got {got}")]
371    Address { expected: Address, got: Address },
372    #[error("Wrong module: expected {expected}, got {got}")]
373    Module {
374        expected: Identifier,
375        got: Identifier,
376    },
377    #[error("Wrong name: expected {expected}, got {got}")]
378    Name {
379        expected: Identifier,
380        got: Identifier,
381    },
382    #[error("Wrong type parameters: {0}")]
383    TypeParams(#[from] TypeParamsError),
384}
385
386#[derive(thiserror::Error, Debug)]
387pub enum TypeParamsError {
388    #[error("Wrong number of generics: expected {expected}, got {got}")]
389    Number { expected: usize, got: usize },
390    #[error("Wrong type for generic: {0}")]
391    TypeTag(Box<TypeTagError>),
392}
393
394impl From<TypeTagError> for TypeParamsError {
395    fn from(value: TypeTagError) -> Self {
396        Self::TypeTag(Box::new(value))
397    }
398}
399
400#[derive(thiserror::Error, Debug)]
401pub enum ParseTypeTagError {
402    #[error("Parsing TypeTag: {0}")]
403    FromStr(#[from] sui_sdk_types::TypeParseError),
404    #[error("Converting from TypeTag: {0}")]
405    TypeTag(#[from] TypeTagError),
406}
407
408#[derive(thiserror::Error, Debug)]
409pub enum ParseStructTagError {
410    #[error("Parsing StructTag: {0}")]
411    FromStr(#[from] sui_sdk_types::TypeParseError),
412    #[error("Converting from StructTag: {0}")]
413    StructTag(#[from] StructTagError),
414}
415
416#[derive(thiserror::Error, Debug)]
417pub enum FromRawTypeError {
418    #[error("Converting from TypeTag: {0}")]
419    TypeTag(#[from] TypeTagError),
420    #[error("Deserializing BCS: {0}")]
421    Bcs(#[from] bcs::Error),
422}
423
424#[derive(thiserror::Error, Debug)]
425pub enum FromRawStructError {
426    #[error("Converting from StructTag: {0}")]
427    StructTag(#[from] StructTagError),
428    #[error("Deserializing BCS: {0}")]
429    Bcs(#[from] bcs::Error),
430}