Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ The format is based on [Keep a Changelog], and this project adheres to
### Added

- Add `sui-move`: core Move-shaped type layer (traits, abilities, decoding).
- Add Move framework primitives under `sui_move::primitives` (e.g. `coin`, `balance`, `vec_map`) with BCS-accurate tags/layouts.
- Keep Move framework declarations out of the `sui-move` core so generated package bindings define
`UID`, `Coin<T>`, `Balance<T>`, containers, and other framework shapes.
- Add `sui-move-derive` and the `sui-move` `derive` feature for defining Move-shaped structs via macros.
- Add `sui-move-call`: typed Move call descriptions (`CallSpec`) plus typed wrappers for Sui `Input` kinds (pure, immutable/owned, shared, receiving).
- Add `sui-move-ptb`: minimal PTB builder that consumes `CallSpec` and produces `ProgrammableTransaction`.
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
bcs = "0.1.6"
sui-sdk-types = { version = "0.3.1", git = "https://github.com/mystenlabs/sui-rust-sdk", features = ["serde"] }
sui-rpc = { version = "0.3.1", git = "https://github.com/mystenlabs/sui-rust-sdk" }
sui-crypto = { version = "0.3.0", git = "https://github.com/mystenlabs/sui-rust-sdk" }
sui-sdk-types = { version = "0.3.1", features = ["serde"] }
sui-rpc = { version = "0.3.1" }
sui-crypto = { version = "0.3.0" }
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }
Expand Down
2 changes: 1 addition & 1 deletion MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Execution and checkpoint inclusion are distinct:
- a transaction can be **executed** (effects exist),
- later it can be **checkpointed** (indexes are updated; “read-your-writes” on that node).

RPC calls may time out waiting for checkpoint inclusion even though execution succeeded. A correct
gRPC calls may time out waiting for checkpoint inclusion even though execution succeeded. A correct
client abstraction must preserve recovery information (digest/effects when available) and represent
finality explicitly rather than as a boolean.

16 changes: 13 additions & 3 deletions sui-move-call/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,14 @@ use sui_move::prelude::*;
use sui_move_call::{CallSpec, MoveObject};
use sui_sdk_types::{Address, Digest, ObjectReference, TypeTag};

#[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
pub struct UID {
pub id: u64,
}

#[sui_move::move_struct(address = "0x1", module = "vault", abilities = "key")]
pub struct Vault {
pub id: sui_move::types::UID,
pub id: UID,
}

pub fn withdraw(vault: &MoveObject<Vault>, amount: u64) -> CallSpec {
Expand Down Expand Up @@ -112,9 +117,14 @@ use std::str::FromStr;
use sui_move_call::{CallArg, CallSpec, ReceivingMoveObject, SharedMoveObject};
use sui_sdk_types::{Address, Digest, ObjectReference};

#[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
struct UID {
id: u64,
}

#[sui_move::move_struct(address = "0x1", module = "demo", abilities = "key")]
struct Thing {
id: sui_move::types::UID,
id: UID,
}

let package = Address::from_str("0x1").unwrap();
Expand All @@ -134,5 +144,5 @@ assert!(matches!(spec.arguments[1], CallArg::Receiving(_)));
## Non-goals

- No transaction building: this crate does not produce `ProgrammableTransaction`.
- No execution/runtime: this crate does not talk to RPC or submit transactions.
- No execution/runtime: this crate does not talk to a network client or submit transactions.
- No object fetching: object contents are not loaded or decoded here.
20 changes: 16 additions & 4 deletions sui-move-call/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ pub use sui_sdk_types::Input as CallArg;
/// use sui_move_call::MoveObject;
/// use sui_sdk_types::{Address, Digest, ObjectReference};
///
/// # #[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
/// # struct UID { id: u64 }
/// #
/// #[sui_move::move_struct(address = "0x1", module = "demo", abilities = "key")]
/// struct Demo {
/// id: sui_move::types::UID,
/// id: UID,
/// }
///
/// let id = Address::from_str("0x1").unwrap();
Expand Down Expand Up @@ -82,9 +85,12 @@ impl<T: MoveStruct + HasKey> MoveObject<T> {
/// use sui_move_call::SharedMoveObject;
/// use sui_sdk_types::Address;
///
/// # #[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
/// # struct UID { id: u64 }
/// #
/// #[sui_move::move_struct(address = "0x1", module = "demo", abilities = "key")]
/// struct SharedThing {
/// id: sui_move::types::UID,
/// id: UID,
/// }
///
/// let object_id = Address::from_str("0x1").unwrap();
Expand Down Expand Up @@ -168,9 +174,12 @@ impl<T: MoveStruct + HasKey> SharedMoveObject<T> {
/// use sui_move_call::ReceivingMoveObject;
/// use sui_sdk_types::{Address, Digest, ObjectReference};
///
/// # #[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
/// # struct UID { id: u64 }
/// #
/// #[sui_move::move_struct(address = "0x1", module = "demo", abilities = "key")]
/// struct ReceivingThing {
/// id: sui_move::types::UID,
/// id: UID,
/// }
///
/// let id = Address::from_str("0x1").unwrap();
Expand Down Expand Up @@ -369,9 +378,12 @@ pub enum CallSpecError {
/// use sui_move_call::{CallSpec, MoveObject};
/// use sui_sdk_types::{Address, Digest, ObjectReference, TypeTag};
///
/// # #[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
/// # struct UID { id: u64 }
/// #
/// #[sui_move::move_struct(address = "0x1", module = "vault", abilities = "key")]
/// struct Vault {
/// id: sui_move::types::UID,
/// id: UID,
/// }
///
/// let package = Address::from_str("0x1").unwrap();
Expand Down
12 changes: 11 additions & 1 deletion sui-move-call/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ use sui_move_call::{
};
use sui_sdk_types::{Address, Digest, FundsWithdrawal, ObjectReference, TypeTag, WithdrawFrom};

#[sui_move::move_struct(address = "0x2", module = "object", abilities = "copy, store")]
struct ID {
bytes: Address,
}

#[sui_move::move_struct(address = "0x2", module = "object", abilities = "store")]
struct UID {
id: ID,
}

#[sui_move::move_struct(address = "0x1", module = "demo", abilities = "key")]
struct Demo {
id: sui_move::types::UID,
id: UID,
}

#[test]
Expand Down
34 changes: 31 additions & 3 deletions sui-move-codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,11 @@ assert!(code.contains("spec.push_type_arg::<T0>();"));
## Example: fetch over gRPC

```rust,no_run
use sui_move_codegen::fetch_package;
use sui_rpc::Client;
use sui_move_codegen::{fetch_package, GrpcClient};
use sui_sdk_types::Address;

# async fn demo() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::new(Client::MAINNET_FULLNODE)?;
let mut client = GrpcClient::new(GrpcClient::MAINNET_FULLNODE)?;
let package_id: Address = "0x2".parse()?;

let pkg = fetch_package(&mut client, package_id).await?;
Expand All @@ -349,6 +348,35 @@ To keep builds deterministic, fetch metadata once and commit it (JSON), then ren

This avoids putting network access in CI/build scripts.

## Cross-package type resolution

The renderer only treats primitive Move types and datatypes from the package being rendered as
intrinsic. Framework and dependency types are still ordinary package declarations, so register their
generated Rust bindings explicitly:

```rust,no_run
use sui_move_codegen::fetch_package;
use sui_move_codegen::render::{render_package, RenderOptions};
use sui_move_codegen::GrpcClient;
use sui_sdk_types::Address;

# async fn demo() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GrpcClient::new(GrpcClient::MAINNET_FULLNODE)?;

let framework = fetch_package(&mut client, "0x2".parse::<Address>()?).await?;
let app = fetch_package(&mut client, "0x123".parse::<Address>()?).await?;

let opts = RenderOptions::default().with_external_package(&framework, "sui_framework");
let code = render_package(&app, &opts);
# let _ = code;
# Ok(())
# }
```

Without an external binding, an external datatype renders as a `compile_error!`. That keeps missing
dependency bindings explicit instead of silently reintroducing handwritten framework mirrors into
`sui-move`.

## Using the generated code

The rendered Rust is meant to live in its own crate or module. At minimum, the generated code
Expand Down
9 changes: 6 additions & 3 deletions sui-move-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ pub mod render;

pub use crate::source::fetch_package;

/// Sui gRPC client used by package metadata fetching.
pub type GrpcClient = sui_rpc::Client;

/// Errors from sourcing or normalizing package metadata.
#[derive(thiserror::Error, Debug)]
pub enum Error {
/// RPC request failed.
#[error("rpc: {0}")]
Rpc(String),
/// gRPC request failed.
#[error("grpc: {0}")]
Grpc(String),

/// Package missing from response.
#[error("package missing from response")]
Expand Down
60 changes: 7 additions & 53 deletions sui-move-codegen/src/render/builtins.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Mapping for common Sui framework types.
//! Mapping for irreducible built-in Move types.
//!
//! Sui framework types such as `0x2::object::UID`, `0x2::coin::Coin`, and
//! `0x1::option::Option` are deliberately not mapped here. They are package-defined datatypes and
//! must be generated from package metadata like any other Move package.

use proc_macro2::TokenStream;
use quote::quote;

use crate::ir::TypeName;

Expand All @@ -15,55 +18,6 @@ pub(crate) struct BuiltinDatatype {
}

pub(crate) fn map_builtin(type_name: &TypeName, use_aliases: bool) -> Option<BuiltinDatatype> {
let address = type_name.address.as_str();
let module = type_name.module.as_str();
let name = type_name.name.as_str();

let (path, is_key) = match (address, module, name) {
("0x2", "object", "UID") => (sm(use_aliases, quote! { types::UID }), false),
("0x2", "object", "ID") => (sm(use_aliases, quote! { types::ID }), false),
("0x2", "sui", "SUI") => (sm(use_aliases, quote! { sui::SUI }), false),
("0x2", "bag", "Bag") => (sm(use_aliases, quote! { bag::Bag }), true),
("0x2", "balance", "Balance") => (sm(use_aliases, quote! { balance::Balance }), false),
("0x2", "coin", "Coin") => (sm(use_aliases, quote! { coin::Coin }), true),
("0x2", "clock", "Clock") => (sm(use_aliases, quote! { clock::Clock }), true),
("0x2", "tx_context", "TxContext") => {
(sm(use_aliases, quote! { tx_context::TxContext }), false)
}
("0x1", "type_name", "TypeName") => {
(sm(use_aliases, quote! { type_name::TypeName }), false)
}
("0x1", "ascii", "String") => (sm(use_aliases, quote! { ascii::String }), false),
("0x2", "vec_map", "VecMap") => (sm(use_aliases, quote! { vec_map::VecMap }), false),
("0x2", "vec_set", "VecSet") => (sm(use_aliases, quote! { vec_set::VecSet }), false),
("0x2", "object_bag", "ObjectBag") => {
(sm(use_aliases, quote! { object_bag::ObjectBag }), true)
}
("0x2", "linked_table", "LinkedTable") => {
(sm(use_aliases, quote! { linked_table::LinkedTable }), false)
}
("0x2", "object_table", "ObjectTable") => {
(sm(use_aliases, quote! { object_table::ObjectTable }), false)
}
("0x1", "option", "Option") => (sm(use_aliases, quote! { containers::MoveOption }), false),
("0x2", "table", "Table") => (sm(use_aliases, quote! { containers::Table }), false),
("0x2", "dynamic_field", "Field") => {
(sm(use_aliases, quote! { containers::DynamicField }), false)
}
("0x2", "dynamic_object_field", "DynamicField") => (
sm(use_aliases, quote! { containers::DynamicObjectField }),
false,
),
_ => return None,
};

Some(BuiltinDatatype { path, is_key })
}

fn sm(use_aliases: bool, path: TokenStream) -> TokenStream {
if use_aliases {
quote! { sm::#path }
} else {
quote! { sui_move::#path }
}
let _ = (type_name, use_aliases);
None
}
3 changes: 3 additions & 0 deletions sui-move-codegen/src/render/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ fn is_object_type(
if let Some(builtin) = builtins::map_builtin(type_name, opts.use_aliases) {
return builtin.is_key;
}
if let Some(external) = opts.external_types.get(type_name) {
return external.is_key;
}
pkg.modules
.get(&type_name.module)
.and_then(|m| {
Expand Down
Loading
Loading