Skip to main content

talus_sui_move/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use serde::de::DeserializeOwned;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::str::FromStr;
7
8#[cfg(feature = "derive")]
9pub use talus_sui_move_derive::{move_module, move_struct};
10
11pub mod prelude {
12    //! Convenient imports for working with this crate.
13    //!
14    //! Intended for end user code and examples.
15    #[cfg(feature = "derive")]
16    pub use crate::{move_module, move_struct};
17    pub use crate::{
18        Copyable, Droppable, HasCopy, HasDrop, HasKey, HasStore, MoveInstance, MoveStruct,
19        MoveType, Storable, U256,
20    };
21    pub use sui_sdk_types::{Address, Identifier, StructTag, TypeTag};
22}
23
24#[doc(hidden)]
25pub mod __private {
26    pub use serde;
27    pub use sui_sdk_types;
28}
29
30mod builtins;
31pub mod decode;
32pub use decode::{decode_copyable, decode_keyed, decode_storable};
33
34/// Move `u256` value backed by [`ethnum::U256`].
35///
36/// Serialization uses the 32 byte little endian representation required by Move rather than the
37/// hexadecimal string representation provided by `ethnum`.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[repr(transparent)]
40pub struct U256(pub ethnum::U256);
41
42impl U256 {
43    /// Construct a `u256` from its little endian byte representation.
44    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
45        Self(ethnum::U256::from_le_bytes(bytes))
46    }
47
48    /// Return the little endian byte representation.
49    pub fn to_le_bytes(self) -> [u8; 32] {
50        self.0.to_le_bytes()
51    }
52}
53
54impl From<ethnum::U256> for U256 {
55    fn from(value: ethnum::U256) -> Self {
56        Self(value)
57    }
58}
59
60impl From<U256> for ethnum::U256 {
61    fn from(value: U256) -> Self {
62        value.0
63    }
64}
65
66impl Serialize for U256 {
67    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        S: serde::Serializer,
70    {
71        self.to_le_bytes().serialize(serializer)
72    }
73}
74
75impl<'de> Deserialize<'de> for U256 {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: serde::Deserializer<'de>,
79    {
80        <[u8; 32]>::deserialize(deserializer).map(Self::from_le_bytes)
81    }
82}
83
84/// A Rust type that corresponds to a Move type.
85///
86/// Implementors provide a static [`TypeTag`](sui_sdk_types::TypeTag) (including any type
87/// arguments). This enables strongly typed construction and verification of Move type tags and
88/// safe BCS decoding.
89///
90/// # Example
91/// ```
92/// use talus_sui_move::prelude::*;
93///
94/// assert_eq!(<u64 as MoveType>::type_tag_static(), TypeTag::U64);
95/// ```
96pub trait MoveType: Serialize + for<'de> Deserialize<'de> + fmt::Debug + PartialEq + Eq {
97    /// Construct the static type tag for this type (including type arguments).
98    fn type_tag_static() -> sui_sdk_types::TypeTag;
99
100    /// Convenience to get the type tag for this value.
101    fn type_tag(&self) -> sui_sdk_types::TypeTag {
102        Self::type_tag_static()
103    }
104
105    /// Serialize this value with BCS.
106    fn to_bcs(&self) -> Result<Vec<u8>, bcs::Error> {
107        bcs::to_bytes(self)
108    }
109
110    /// Deserialize a value of this type from BCS bytes.
111    fn from_bcs(bytes: &[u8]) -> Result<Self, bcs::Error>
112    where
113        Self: Sized,
114    {
115        bcs::from_bytes(bytes)
116    }
117}
118
119/// A Move struct type (including any type parameters).
120///
121/// Move structs have both a [`TypeTag`](sui_sdk_types::TypeTag) and a
122/// [`StructTag`](sui_sdk_types::StructTag).
123///
124pub trait MoveStruct: MoveType {
125    /// Construct the static struct tag (including type arguments).
126    fn struct_tag_static() -> sui_sdk_types::StructTag;
127
128    fn struct_tag(&self) -> sui_sdk_types::StructTag {
129        Self::struct_tag_static()
130    }
131}
132
133/// Marker trait for the Move `key` ability.
134pub trait HasKey {}
135
136/// Marker trait for the Move `store` ability.
137pub trait HasStore {}
138
139/// Marker trait for the Move `copy` ability.
140pub trait HasCopy: Clone {}
141
142/// Marker trait for the Move `drop` ability.
143pub trait HasDrop {}
144
145/// A convenient bound for types that have the `store` ability.
146pub trait Storable: MoveType + HasStore {}
147impl<T: MoveType + HasStore> Storable for T {}
148
149/// A convenient bound for types that have `copy` and `drop`.
150pub trait Copyable: MoveType + HasCopy + HasDrop + Clone {}
151impl<T: MoveType + HasCopy + HasDrop + Clone> Copyable for T {}
152
153/// A convenient bound for types that have the `drop` ability.
154pub trait Droppable: MoveType + HasDrop {}
155impl<T: MoveType + HasDrop> Droppable for T {}
156
157/// Errors that can occur when verifying or decoding Move data.
158#[derive(thiserror::Error, Debug)]
159pub enum DecodeError {
160    #[error("type tag mismatch. expected {expected:?}, got {got:?}")]
161    TypeTagMismatch {
162        expected: sui_sdk_types::TypeTag,
163        got: sui_sdk_types::TypeTag,
164    },
165    #[error(transparent)]
166    Bcs(#[from] bcs::Error),
167    #[error("failed to parse identifier: {0}")]
168    Identifier(String),
169    #[error("failed to parse address: {0}")]
170    Address(String),
171}
172
173/// A value paired with the Move type tag it was accompanied by.
174///
175/// This is useful when you receive `(type_tag, bcs_bytes)` from the chain and want to both verify
176/// the tag and decode the value.
177///
178/// # Example
179/// ```
180/// use talus_sui_move::{MoveInstance, MoveType};
181///
182/// let value = 7u64;
183/// let bytes = value.to_bcs().unwrap();
184///
185/// let inst = MoveInstance::<u64>::from_raw_type(<u64 as MoveType>::type_tag_static(), &bytes)
186///     .unwrap();
187/// assert_eq!(inst.value, 7);
188/// ```
189#[derive(Clone, Debug, PartialEq, Eq)]
190pub struct MoveInstance<T: MoveType> {
191    pub type_tag: sui_sdk_types::TypeTag,
192    pub value: T,
193}
194
195impl<T: MoveType + DeserializeOwned> MoveInstance<T> {
196    /// Decode from raw type tag + BCS bytes, verifying that the tag matches the type.
197    pub fn from_raw_type(
198        type_tag: sui_sdk_types::TypeTag,
199        bytes: &[u8],
200    ) -> Result<Self, DecodeError> {
201        let expected = T::type_tag_static();
202        if type_tag != expected {
203            return Err(DecodeError::TypeTagMismatch {
204                expected,
205                got: type_tag,
206            });
207        }
208        let value = T::from_bcs(bytes)?;
209        Ok(Self { type_tag, value })
210    }
211}
212
213/// Parse a Move identifier (e.g. module or struct name).
214///
215/// This is primarily a convenience for building `StructTag`s in manual [`MoveStruct`] impls.
216///
217/// # Example
218/// ```
219/// use talus_sui_move::parse_identifier;
220///
221/// assert_eq!(parse_identifier("coin").unwrap().to_string(), "coin");
222/// ```
223pub fn parse_identifier(value: &str) -> Result<sui_sdk_types::Identifier, DecodeError> {
224    sui_sdk_types::Identifier::from_str(value)
225        .map_err(|_| DecodeError::Identifier(value.to_string()))
226}
227
228/// Parse a Sui address (e.g. `"0x2"`).
229///
230/// # Example
231/// ```
232/// use std::str::FromStr;
233/// use talus_sui_move::parse_address;
234/// use sui_sdk_types::Address;
235///
236/// assert_eq!(
237///     parse_address("0x2").unwrap(),
238///     Address::from_str("0x2").unwrap()
239/// );
240/// ```
241pub fn parse_address(value: &str) -> Result<sui_sdk_types::Address, DecodeError> {
242    sui_sdk_types::Address::from_str(value).map_err(|_| DecodeError::Address(value.to_string()))
243}
244
245/// Convenience helper to get a static `TypeTag` for any `MoveType`.
246///
247/// # Example
248/// ```
249/// use talus_sui_move::{type_tag_of, MoveType};
250///
251/// assert_eq!(type_tag_of::<u64>(), <u64 as MoveType>::type_tag_static());
252/// ```
253pub fn type_tag_of<T: MoveType>() -> sui_sdk_types::TypeTag {
254    T::type_tag_static()
255}