iota-sdk-move-types
Rust representations of Move types used by the IOTA blockchain.
Each top-level module mirrors one on-chain system package, with every Move
source module mirrored 1:1 as a Rust pub mod:
| Module | Package ID | Move package |
|---|---|---|
move_stdlib |
0x1 |
Move standard library |
iota_framework |
0x2 |
IOTA framework |
iota_system |
0x3 |
IOTA system |
stardust |
0x107a |
Stardust migration |
The Move sources these mirror live in the iota monorepo under
crates/iota-framework.
Compiled Move packages (src/packages_compiled/, not committed)
The move_shape_compare test reads the compiled bytecode blobs of the
four packages above (move-stdlib, iota-framework, iota-system,
stardust) plus published_api.txt, the upstream public-API manifest
(filtered to public struct / public enum records), at test run time.
It parses each Move struct/enum out of the bytecode and verifies that the
corresponding Rust mirror's wire layout matches. These tests are native-only
(no std::fs on wasm32, and their checks are target-independent); the BCS
roundtrip and tag-validation tests still run under wasm.
These artifacts are not committed — they are fetched from
crates/iota-framework/ in the iota monorepo into the gitignored
src/packages_compiled/ directory, at the monorepo commit pinned by the
move-binary-format dev-dependency rev in this crate's Cargo.toml (the
single source of truth: the parser must match the blobs it parses). The
test make targets (crate-level and repo-root) fetch them automatically
when they are missing or were fetched at a different rev than the pin,
so CI and local runs always test against the exact same bytes. The crate
compiles without the artifacts; if you bypass make (e.g. plain
cargo nextest run) without them, the shape-compare tests fail with a
message pointing at the fetch command:
The script needs curl, cargo, and jq.
The completeness contract
The registry_matches_published_api test enforces that every public
struct/enum in the fetched published_api.txt has a registered Rust
mirror. The nightly drift workflow
(.github/workflows/move_types_drift_nightly.yml) diffs the manifest's type
surface at the pinned rev against upstream develop HEAD; a red nightly
means upstream types changed and the mirror set is out of date — not
that the crate is broken. Pull requests are deliberately unaffected by
upstream drift: they keep testing against the pinned rev until the pin
moves.
The catch-up workflow when the nightly turns red:
- Review the diff in the workflow log.
- Add/update the Rust mirrors (and
entry!registrations) it points at. - Bump the
move-binary-formatrev in this crate'sCargo.tomlto the new monorepo SHA — this single rev pins both the parser and the artifact fetch. - Run
make update-compiled-packagesandmake test, then open a PR — its CI validates the new mirrors against the new rev.
System packages change rarely, so the expected cadence is a small catch-up PR a few times a year, with at most one day of detection delay.
Refreshing the BCS test fixtures
The roundtrip tests in tests/fixture_roundtrip.rs decode real on-chain
BCS bytes (committed under tests/fixtures/*.bcs) into the hand-curated
type mirrors and re-encode to assert byte-for-byte equality.
To refresh those fixtures against current chain state:
The capture binary lives in the iota-sdk crate (it needs the GraphQL
client), but it writes back into this crate's tests/fixtures/.
This queries the IOTA mainnet GraphQL endpoint, re-fetches each pinned
object (and dynamic field), and overwrites tests/fixtures/*.bcs. Use
IOTA_NETWORK=testnet / IOTA_NETWORK=devnet to capture against a
different network instead.
Most fixtures are pinned to specific object IDs and produce byte-for-byte
identical output across runs. The exceptions are clock.bcs (live
timestamp) and iota_system_state_inner_v2.bcs (changes at every epoch
boundary).
Adding a new fixture
- Add an entry to
FIXTURESincrates/iota-sdk/examples/capture_move_type_fixtures.rs. UseSource::TypeFilter("0x…::module::Type")if you don't have an ObjectId yet. - Run the capture binary. For
Source::TypeFilterentries, it prints the discovered ObjectId — copy it back into the fixture entry as aSource::ObjectId(…)pin so re-runs are stable. - Add a corresponding
#[test]totests/fixture_roundtrip.rs.