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