8 releases
new 0.8.4 | Jan 29, 2025 |
---|---|
0.8.3 | Jan 27, 2025 |
0.7.2 | Jan 14, 2025 |
#82 in Magic Beans
769 downloads per month
Used in 8 crates
185KB
4K
SLoC
Exports the sui_pkg_sdk!
macro for generating Rust types from Move
source code and implementing relevant af_move_type
traits.
Automates the conversion of Sui Move types to Rust. The goal is to extract as much information as possible at compile time about the Move types of a Sui package, generating equivalent Rust types that:
- are BCS-compatible with their on-chain counterparts, so that their contents can be deserialized from BCS bytes returned by RPCs
- embed type information based on their location (path) in a Move package + type parameters, so that a corresponding type tag can be easily constructed with just the missing information
- use the embedded type information when deserializing a
MoveInstance
to verify the type of incoming data, to avoid mistakenly deserializing a different type that has the same BCS bytes
See also:
af_move_type
af_move_type_derive
for how the type tag information for a struct is derived from its declaration
Move types to Rust types
This macro allows callers to almost copy and paste Move struct declarations and get equivalent Rust types. Some additional steps may be necessary however:
- If
phantom
keywords are present, they must be substituted by!phantom
- Struct fields should be Rust types. That means they must be in scope. Special Move types like
address
,vector<T>
andu256
are automatically converted to equivalent Rust types.
The only requirement for a struct field type is that it has the same BCS representation as
the Move type for that field. You may use that to your advantage. For instance, if a
u256
is supposed to be interpreted as a fixed point number, you may define a custom
FixedP(U256)
type that (de)serializes to/from u256
bytes but behaves like a fixed point
number.
Additionally, you may add any outter attributes, e.g. docs, to structs and their fields.
All MoveStruct
s created by this macro will have a pretty Display
using tabled
as a backend.
Examples
use af_sui_pkg_sdk::sui_pkg_sdk;
sui_pkg_sdk!(package {
module clearing_house {
/// Used to dynamically load market objects as needed.
/// Used to dynamically load traders' position objects as needed.
struct ClearingHouse<!phantom T> has key {
id: UID,
// ...
}
/// Stores all deposits from traders for collateral T.
/// Stores the funds reserved for covering bad debt from untimely
/// liquidations.
///
/// The Clearing House keeps track of who owns each share of the vault.
struct Vault<!phantom T> has key, store {
id: UID,
collateral_balance: Balance<T>,
insurance_fund_balance: Balance<T>,
scaling_factor: u64
}
}
module keys {
/// Key type for accessing trader position in clearing house.
struct Position has copy, drop, store {
account_id: u64,
}
}
});
Rust types clearing_house::{ClearingHouse, Vault}
and keys::Position
will be generated from
the macro call above.
Now suppose we have received a type tag and BCS contents of a Move object from an RPC call. We
can try deserializing it into a MoveInstance
of one of these generated types
use af_move_type::{MoveInstance, otw::Otw};
let type_tag: TypeTag;
let base64_bcs: String;
let instance = MoveInstance::<Vault<Otw>>::from_raw_type(
type_tag,
&af_sui_types::decode_base64_default(base64_bcs)?
)?;
println!("Coin type {}", instance.type_.t);
A few things are happening here:
from_raw_type
is checking first thattype_tag
matches the declaration of the struct, i.e., it is of the form_::clearing_house::Vault<_::_::_>
. Anything else will fail immediately- then, it tries to deserialize
clearing_house::Vault
from the BCS bytes
Finally, notice that we're accessing a type_
field in the Move instance. That's because a
VaultTypeTag
was automatically generated:
pub struct VaultTypeTag<T: MoveType> {
pub address: Address,
pub t: <T as MoveType>::TypeTag,
}
Notice this contains information about the Vault
's type tag that couldn't be derived at
compile time, namely, the address of the Move package defining the struct and the concrete type
of the generic type parameter.
One advantage of this type tag over carrying around the generic StructTag
is that we can
access the type of the OTW directly, while to do so with the latter we'd have to check if
StructTag::type_params
is not empty every time.
Dependencies
~12MB
~229K SLoC