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}
107
108impl<T> MoveTypeTag for T where
109    T: Into<TypeTag>
110        + TryFrom<TypeTag, Error = TypeTagError>
111        + FromStr
112        + Clone
113        + Debug
114        + PartialEq
115        + Eq
116        + Hash
117        + for<'de> Deserialize<'de>
118        + PartialOrd
119        + Ord
120        + Serialize
121{
122}
123
124// =============================================================================
125//  MoveStruct
126// =============================================================================
127
128/// Trait marking a Move struct type. Has a specific way to construct a `StructTag`.
129pub trait MoveStruct: MoveType<TypeTag = Self::StructTag> {
130    type StructTag: MoveStructTag;
131}
132
133pub trait MoveStructTag:
134    Into<StructTag> + TryFrom<StructTag, Error = StructTagError> + MoveTypeTag
135{
136}
137
138impl<T> MoveStructTag for T where
139    T: Into<StructTag> + TryFrom<StructTag, Error = StructTagError> + MoveTypeTag
140{
141}
142
143// =============================================================================
144//  Abilities
145// =============================================================================
146
147pub trait HasKey: MoveStruct {
148    fn object_id(&self) -> Address;
149}
150
151pub trait HasCopy: MoveStruct + Copy {}
152
153pub trait HasStore: MoveStruct {}
154
155pub trait HasDrop: MoveStruct {}
156
157// =============================================================================
158//  Static attributes
159// =============================================================================
160
161/// Move type for which the type tag can be derived at compile time.
162pub trait StaticTypeTag: MoveType {
163    fn type_() -> Self::TypeTag;
164
165    fn type_tag() -> TypeTag {
166        Self::type_().into()
167    }
168}
169
170/// Move struct for which the address of the package is known at compile time.
171pub trait StaticAddress: MoveStruct {
172    fn address() -> Address;
173}
174
175/// Move struct for which the module in the package is known at compile time.
176pub trait StaticModule: MoveStruct {
177    fn module() -> Identifier;
178}
179
180/// Move struct for which the name of object is known at compile time.
181pub trait StaticName: MoveStruct {
182    fn name() -> Identifier;
183}
184
185/// Move struct for which the type args of object are known at compile time.
186pub trait StaticTypeParams: MoveStruct {
187    fn type_params() -> Vec<TypeTag>;
188}
189
190/// Move struct for which the struct tag can be derived at compile time.
191pub trait StaticStructTag: MoveStruct {
192    fn struct_tag() -> StructTag;
193}
194
195impl<T> StaticStructTag for T
196where
197    T: StaticAddress + StaticModule + StaticName + StaticTypeParams,
198{
199    fn struct_tag() -> StructTag {
200        StructTag::new(
201            Self::address(),
202            Self::module(),
203            Self::name(),
204            Self::type_params(),
205        )
206    }
207}
208
209// =============================================================================
210//  MoveInstance
211// =============================================================================
212
213/// Represents an instance of a Move type.
214///
215/// Both `type_` and `value` are necessary to represent an instance since otherwise there would be
216/// ambiguity, e.g., when the same package is published twice on-chain.
217#[derive(Clone, Debug, PartialEq, Eq, Hash)]
218pub struct MoveInstance<T: MoveType> {
219    pub type_: T::TypeTag,
220    pub value: T,
221}
222
223impl<T: StaticTypeTag> From<T> for MoveInstance<T> {
224    fn from(value: T) -> Self {
225        Self {
226            type_: T::type_(),
227            value,
228        }
229    }
230}
231
232impl<T: MoveStruct + tabled::Tabled> std::fmt::Display for MoveInstance<T> {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        use tabled::Table;
235        use tabled::settings::panel::Header;
236        use tabled::settings::{Rotate, Settings, Style};
237
238        let stag: StructTag = self.type_.clone().into();
239        let settings = Settings::default()
240            .with(Rotate::Left)
241            .with(Rotate::Top)
242            .with(Style::rounded())
243            .with(Header::new(stag.to_string()));
244        let mut table = Table::new([&self.value]);
245        table.with(settings);
246        write!(f, "{table}")
247    }
248}
249
250impl std::fmt::Display for MoveInstance<Address> {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(f, "{}", self.value)
253    }
254}
255
256macro_rules! impl_primitive_move_instance_display {
257    ($($type:ty)+) => {$(
258        impl std::fmt::Display for MoveInstance<$type> {
259            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260                write!(f, "{}", self.value)
261            }
262        }
263    )+};
264}
265
266impl_primitive_move_instance_display! {
267    bool
268    u8
269    u16
270    u32
271    u64
272    u128
273    U256
274}
275
276impl<T: MoveType> MoveInstance<T> {
277    /// Convenience function for constructing from raw RPC-returned general types.
278    pub fn from_raw_type(tag: TypeTag, bytes: &[u8]) -> Result<Self, FromRawTypeError> {
279        Ok(Self {
280            type_: tag.try_into()?,
281            value: T::from_bcs(bytes)?,
282        })
283    }
284}
285
286impl<T: MoveStruct> MoveInstance<T> {
287    /// Convenience function for constructing from raw RPC-returned structs.
288    pub fn from_raw_struct(stag: StructTag, bytes: &[u8]) -> Result<Self, FromRawStructError> {
289        Ok(Self {
290            type_: stag.try_into()?,
291            value: T::from_bcs(bytes)?,
292        })
293    }
294}
295
296fn number_to_string_value_recursive(value: &mut serde_json::Value) {
297    match value {
298        serde_json::Value::Array(a) => {
299            for v in a {
300                number_to_string_value_recursive(v)
301            }
302        }
303        serde_json::Value::Number(n) => *value = serde_json::Value::String(n.to_string()),
304        serde_json::Value::Object(o) => {
305            for v in o.values_mut() {
306                number_to_string_value_recursive(v)
307            }
308        }
309        _ => (),
310    }
311}
312
313// =============================================================================
314//  ObjectExt
315// =============================================================================
316
317/// Extract and parse a [`MoveStruct`] from a Sui object.
318pub trait ObjectExt {
319    /// Extract and parse a [`MoveStruct`] from a Sui object.
320    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError>;
321}
322
323impl ObjectExt for sui_sdk_types::Object {
324    fn struct_instance<T: MoveStruct>(&self) -> Result<MoveInstance<T>, ObjectError> {
325        let sui_sdk_types::ObjectData::Struct(s) = self.data() else {
326            return Err(ObjectError::NotStruct);
327        };
328        MoveInstance::from_raw_struct(s.object_type().clone(), s.contents()).map_err(From::from)
329    }
330}
331
332/// Error for [`ObjectExt`].
333#[derive(thiserror::Error, Debug)]
334pub enum ObjectError {
335    #[error("Object is not a Move struct")]
336    NotStruct,
337    #[error(transparent)]
338    FromRawStruct(#[from] FromRawStructError),
339}
340
341// =============================================================================
342//  Errors
343// =============================================================================
344
345#[derive(thiserror::Error, Debug)]
346pub enum TypeTagError {
347    #[error("Wrong TypeTag variant: expected {expected}, got {got}")]
348    Variant { expected: String, got: TypeTag },
349    #[error("StructTag params: {0}")]
350    StructTag(#[from] StructTagError),
351}
352
353#[derive(thiserror::Error, Debug)]
354pub enum StructTagError {
355    #[error("Wrong address: expected {expected}, got {got}")]
356    Address { expected: Address, got: Address },
357    #[error("Wrong module: expected {expected}, got {got}")]
358    Module {
359        expected: Identifier,
360        got: Identifier,
361    },
362    #[error("Wrong name: expected {expected}, got {got}")]
363    Name {
364        expected: Identifier,
365        got: Identifier,
366    },
367    #[error("Wrong type parameters: {0}")]
368    TypeParams(#[from] TypeParamsError),
369}
370
371#[derive(thiserror::Error, Debug)]
372pub enum TypeParamsError {
373    #[error("Wrong number of generics: expected {expected}, got {got}")]
374    Number { expected: usize, got: usize },
375    #[error("Wrong type for generic: {0}")]
376    TypeTag(Box<TypeTagError>),
377}
378
379impl From<TypeTagError> for TypeParamsError {
380    fn from(value: TypeTagError) -> Self {
381        Self::TypeTag(Box::new(value))
382    }
383}
384
385#[derive(thiserror::Error, Debug)]
386pub enum ParseTypeTagError {
387    #[error("Parsing TypeTag: {0}")]
388    FromStr(#[from] sui_sdk_types::TypeParseError),
389    #[error("Converting from TypeTag: {0}")]
390    TypeTag(#[from] TypeTagError),
391}
392
393#[derive(thiserror::Error, Debug)]
394pub enum ParseStructTagError {
395    #[error("Parsing StructTag: {0}")]
396    FromStr(#[from] sui_sdk_types::TypeParseError),
397    #[error("Converting from StructTag: {0}")]
398    StructTag(#[from] StructTagError),
399}
400
401#[derive(thiserror::Error, Debug)]
402pub enum FromRawTypeError {
403    #[error("Converting from TypeTag: {0}")]
404    TypeTag(#[from] TypeTagError),
405    #[error("Deserializing BCS: {0}")]
406    Bcs(#[from] bcs::Error),
407}
408
409#[derive(thiserror::Error, Debug)]
410pub enum FromRawStructError {
411    #[error("Converting from StructTag: {0}")]
412    StructTag(#[from] StructTagError),
413    #[error("Deserializing BCS: {0}")]
414    Bcs(#[from] bcs::Error),
415}