iota_sdk_move_types/lib.rs
1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Rust representations of Move types used by the IOTA blockchain.
5//!
6//! Each top-level module corresponds to a system package, identified by the
7//! address constants on [`iota_types::Address`]:
8//!
9//! - [`move_stdlib`] — `0x1`, the Move standard library
10//! - [`iota_framework`] — `0x2`, the IOTA framework
11//! - [`iota_system`] — `0x3`, the IOTA system package
12//! - [`stardust`] — `0x107a`, the Stardust migration package
13//!
14//! Inside each package, every Move source module is mirrored 1:1 as a Rust
15//! `pub mod`. Generic Move types stay generic in Rust (with a
16//! `PhantomData<T>` placeholder for phantom parameters).
17
18#[macro_use]
19mod macros;
20
21mod packages;
22pub use packages::{iota_framework, iota_system, move_stdlib, stardust};
23
24// The shape machinery (this module, the `MoveShape` derives on every
25// mirror, and the comparator below) is native-only: the comparator reads
26// the fetched package artifacts from disk at test time (no `std::fs` on
27// wasm32), and its checks are target-independent — running them on one
28// target covers all.
29#[cfg(all(test, not(target_arch = "wasm32")))]
30mod move_shape;
31
32#[cfg(all(test, feature = "serde", not(target_arch = "wasm32")))]
33mod move_shape_compare;
34
35/// A Rust type that knows the Move type tag it represents.
36///
37/// Generic mirrors like [`iota_framework::coin::CoinMetadata`] or
38/// [`stardust::basic_output::BasicOutput`] take a marker type (e.g.
39/// [`iota_framework::iota::IOTA`]) as their phantom type argument. Implementing
40/// this trait declares which on-chain type the marker represents, which
41/// lets the `try_from_object` constructors verify the object's type tag
42/// against `T`. The marker is phantom, so the BCS bytes of e.g. a
43/// `BasicOutput<IOTA>` and a `BasicOutput<OTHER>` are identical — the type
44/// tag is the only place the coin type is recorded, and without this check
45/// one would silently decode as the other.
46///
47/// To decode objects holding your own coin type, define an empty marker
48/// struct and implement this trait for it. The `try_from_object`
49/// constructors also require `T: serde::de::DeserializeOwned` (an artifact
50/// of the serde derive on the generic mirrors — the phantom marker itself
51/// is never deserialized), so derive `Deserialize` as well:
52///
53/// ```
54/// #[derive(serde::Deserialize)]
55/// struct FOO;
56///
57/// impl iota_sdk_move_types::MoveType for FOO {
58/// fn type_tag() -> iota_types::TypeTag {
59/// "0x123::foo::FOO".parse().unwrap()
60/// }
61/// }
62/// ```
63///
64/// For coin types only known at runtime, use the
65/// `try_from_object_with_type` constructors instead, which take the
66/// expected [`TypeTag`](iota_types::TypeTag) as a value.
67#[cfg(feature = "serde")]
68pub trait MoveType {
69 /// The Move type tag this type represents (e.g. `0x2::iota::IOTA`).
70 fn type_tag() -> iota_types::TypeTag;
71}
72
73/// Error returned when converting an `Object` into a typed mirror.
74///
75/// Every mirror's `TryFrom<&Object>` (and `try_from_object_with_type`)
76/// conversion returns this on failure: the object either isn't a Move
77/// struct, carries a type tag that doesn't match the expected type, or has
78/// BCS contents that fail to decode.
79#[cfg(feature = "serde")]
80#[derive(Debug, thiserror::Error)]
81#[non_exhaustive]
82pub enum FromObjectError {
83 /// The object is a package, not a Move struct.
84 #[error("object is not a Move struct")]
85 NotAMoveStruct,
86 /// The Move struct's type tag does not match the expected type.
87 #[error("object's type tag does not match expected type")]
88 WrongType,
89 /// BCS decoding of the struct contents failed.
90 #[error("bcs decoding failed: {0}")]
91 Bcs(#[from] bcs::Error),
92}