Skip to main content

af_move_type/
lib.rs

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