diff --git a/packages/pas/Move.lock b/packages/pas/Move.lock index b300e81..e6ea47d 100644 --- a/packages/pas/Move.lock +++ b/packages/pas/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "563c15820b27dec9cbe75f826a3b6243ef44da1a" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "b38bca86f0323b3fe8b6b7f4ca0cd7ae7faebe4b" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "563c15820b27dec9cbe75f826a3b6243ef44da1a" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "b38bca86f0323b3fe8b6b7f4ca0cd7ae7faebe4b" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } diff --git a/packages/testing/demo_usd/sources/demo_usd.move b/packages/pas/examples/demo_usd.move similarity index 89% rename from packages/testing/demo_usd/sources/demo_usd.move rename to packages/pas/examples/demo_usd.move index 9c969d7..dd68575 100644 --- a/packages/testing/demo_usd/sources/demo_usd.move +++ b/packages/pas/examples/demo_usd.move @@ -6,19 +6,18 @@ /// This module defines a DEMO_USD witness type that gets registered in the PAS system /// during package initialization. It sets up a Policy with resolution commands for /// SendFunds and UnlockFunds actions. -module demo_usd::demo_usd; - -use pas::namespace::Namespace; -use pas::policy::{Self, Policy, PolicyCap}; -use pas::request::Request; -use pas::send_funds::SendFunds; -use pas::templates::Templates; +module pas::demo_usd; + +use pas::{ + namespace::Namespace, + policy::{Self, Policy, PolicyCap}, + request::Request, + send_funds::SendFunds, + templates::{PAS, Templates} +}; use ptb::ptb; use std::type_name; -use sui::balance::Balance; -use sui::clock::Clock; -use sui::coin::TreasuryCap; -use sui::coin_registry::{Self, MetadataCap}; +use sui::{balance::Balance, clock::Clock, coin::TreasuryCap, coin_registry::{Self, MetadataCap}}; #[error(code = 0)] const EInvalidAmount: vector = b"Any amount over 10K is not allowed in this demo."; @@ -97,7 +96,7 @@ entry fun setup(namespace: &mut Namespace, templates: &mut Templates, faucet: &m type_name.address_string().to_string(), "demo_usd", "approve_transfer", - vector[ptb::ext_input("pas:request"), ptb::object_by_id(@0x6.to_id())], + vector[ptb::ext_input("request"), ptb::object_by_id(@0x6.to_id())], vector[(*type_name.as_string()).to_string()], ); @@ -115,7 +114,7 @@ public fun use_v2( type_name::with_defining_ids().address_string().to_string(), "demo_usd", "approve_transfer_v2", - vector[ptb::ext_input("pas:request"), ptb::object_by_id(object::id(faucet))], + vector[ptb::ext_input("request"), ptb::object_by_id(object::id(faucet))], vector[], ); diff --git a/packages/pas/sources/templates.move b/packages/pas/sources/templates.move index 54b90ac..979b457 100644 --- a/packages/pas/sources/templates.move +++ b/packages/pas/sources/templates.move @@ -12,6 +12,9 @@ use sui::{derived_object, dynamic_field}; #[error(code = 0)] const ETemplateNotSet: vector = b"Template not set for this action."; +/// Namespacing type for `ext_input`'s. +public struct PAS {} + public struct Templates has key { id: UID, } diff --git a/packages/ptb/Move.lock b/packages/ptb/Move.lock index 4ab5128..fa5d7d6 100644 --- a/packages/ptb/Move.lock +++ b/packages/ptb/Move.lock @@ -5,13 +5,13 @@ version = 4 [pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "563c15820b27dec9cbe75f826a3b6243ef44da1a" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "b38bca86f0323b3fe8b6b7f4ca0cd7ae7faebe4b" } use_environment = "testnet" manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" deps = {} [pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "563c15820b27dec9cbe75f826a3b6243ef44da1a" } +source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "b38bca86f0323b3fe8b6b7f4ca0cd7ae7faebe4b" } use_environment = "testnet" manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" deps = { MoveStdlib = "MoveStdlib" } diff --git a/packages/ptb/sources/ptb.move b/packages/ptb/sources/ptb.move index d953a04..85df124 100644 --- a/packages/ptb/sources/ptb.move +++ b/packages/ptb/sources/ptb.move @@ -5,13 +5,19 @@ use std::bcs; use std::string::String; use std::type_name; -// TODO: what should be a standard delimiter for namespaces? const OBJECT_BY_ID_EXT: vector = b"object_by_id:"; const OBJECT_BY_TYPE_EXT: vector = b"object_by_type:"; const RECEIVING_BY_ID_EXT: vector = b"receiving_by_id:"; +/// Tag for extended arguments. Intentionally offset to not be mistaken for a +/// command tag. +const EXT_TAG: u8 = 100; + +/// Defines a PTB template. Unlike the canonical Sui PTB, this one does not have inputs. +/// Instead, the inputs are passed in as arguments to the commands directly. And +/// while this is a bit more verbose and less optimized storage wise, it is more +/// flexible format for off-chain handling. public struct Transaction has copy, drop, store { - inputs: vector, commands: vector, } @@ -110,7 +116,11 @@ public enum CallArg has copy, drop, store { }, /// Extended arguments for off-chain resolution. /// Can be created and registered in a transaction through `ext_input`. - Ext(String), + /// + /// Extended arguments are namespaced by Type associated with them. In an + /// application, this can be the root object, or a special type used for off + /// chain resolution. + Ext(String, String), } /// Defines a simplified `ObjectArg` type for the `Transaction`. @@ -144,7 +154,7 @@ public enum WithdrawFrom has copy, drop, store { /// Create a new Transaction builder. public fun new(): Transaction { - Transaction { inputs: vector[], commands: vector[] } + Transaction { commands: vector[] } } // === System Objects === @@ -158,6 +168,15 @@ public fun random(): Argument { object_by_id(@0x8.to_id()) } /// Shorthand for `object_by_id` with `0xD` (DisplayRegistry). public fun display(): Argument { object_by_id(@0xD.to_id()) } +/// Shorthand for `object_by_id` with `0x403` (DenyList). +public fun deny_list(): Argument { object_by_id(@0x403.to_id()) } + +/// Shorthand for `object_by_id` with `0xC` (CoinRegistry). +public fun coin_registry(): Argument { object_by_id(@0xC.to_id()) } + +/// Shorthand for `object_by_id` with `0xACC` (AccumulatorRoot). +public fun accumulator_root(): Argument { object_by_id(@0xacc.to_id()) } + // === Inputs === /// Create a gas coin input. @@ -248,8 +267,16 @@ public fun receiving_object_by_id(id: ID): Argument { /// Create an external input handler. /// Expected to be understood by the off-chain tooling. -public fun ext_input(name: String): Argument { - Argument::Input(CallArg::Ext(name)) +public fun ext_input(name: String): Argument { + Argument::Input( + CallArg::Ext((*type_name::with_original_ids().as_string()).to_string(), name), + ) +} + +/// Create an external input handler for a given type T. +/// This can be used to hardcode the namespace value without having access to `T`. +public fun ext_input_raw(namespace: String, name: String): Argument { + Argument::Input(CallArg::Ext(namespace, name)) } /// Register a command in the Transaction builder. Returns the Argument, which @@ -337,7 +364,7 @@ public fun upgrade( /// Create an `Ext` command. public fun ext(data: vector): Command { - Command(7, data) + Command(EXT_TAG, data) } // === Test Features === diff --git a/packages/ptb/tests/ptb_tests.move b/packages/ptb/tests/ptb_tests.move index 26756fc..ea850bf 100644 --- a/packages/ptb/tests/ptb_tests.move +++ b/packages/ptb/tests/ptb_tests.move @@ -2,7 +2,15 @@ module ptb::ptb_tests; use ptb::ptb; +use std::type_name; use std::unit_test::assert_eq; +use sui::kiosk::Kiosk; + +/// Keeping this for testing purposes. +public struct PAS {} + +/// NFT type for kiosk example. +public struct NFT has key, store { id: UID } #[test] fun ptb() { @@ -18,27 +26,160 @@ fun ptb() { ), ); - let coin = ptb.command(ptb::split_coins(ptb::gas(), vector[arg])); + // split twice + let coins = ptb.command(ptb::split_coins(ptb::gas(), vector[arg, arg])); - ptb.command(ptb::transfer_objects(vector[coin], ptb::pure(@0))); + ptb.command(ptb::transfer_objects(vector[coins.nested(0), coins.nested(1)], ptb::pure(@0))); assert_eq!(arg.idx(), 0); } +#[test] +fun simple_option_and_vec_operations() { + let mut ptb = ptb::new(); + + let some_arg = ptb.command( + ptb::move_call( + @0x1.to_string(), + "option", + "some", + vector[ptb::pure(100u64)], + vector["u64"], + ), + ); + + // dummy bool result + let _ = ptb.command( + ptb::move_call( + @0x1.to_string(), + "option", + "is_some", + vector[some_arg], + vector["u64"], + ), + ); + + let u64_val = ptb.command( + ptb::move_call( + @0x1.to_string(), + "option", + "swap", + vector[some_arg, ptb::pure(200u64)], + vector["u64"], + ), + ); + + let vec = ptb.command( + ptb::make_move_vec(option::some("u64"), vector[u64_val, ptb::pure(300u64)]), + ); + + 2u8.do!( + |_| ptb.command( + ptb::move_call( + @0x1.to_string(), + "vector", + "pop_back", + vector[vec], + vector["u64"], + ), + ), + ); + + // lastly, destroy empty vector + ptb.command( + ptb::move_call( + @0x1.to_string(), + "vector", + "destroy_empty", + vector[vec], + vector["u64"], + ), + ); +} + #[test] fun pas_command_with_ext_inputs() { ptb::move_call( @0x0.to_string(), "demo_usd", "resolve_transfer", - vector[ - ptb::ext_input("request"), // TODO: consider namespaces here? - ptb::ext_input("policy_arg"), - ptb::clock(), - ], + vector[ptb::ext_input("request"), ptb::ext_input("policy_arg"), ptb::clock()], vector["magic::usdc_app::DEMO_USDC"], ); +} + +#[test] +fun kiosk_transaction_with_rules_resolution() { + let mut ptb = ptb::new(); + let nft_type = (*type_name::with_original_ids().as_string()).to_string(); + + let paid = ptb.command( + ptb::move_call( + @0x2.to_string(), + "transfer_policy", + "paid", + vector[ptb::ext_input("request")], + vector[nft_type], + ), + ); - // TODO: compiler panic! - // std::debug::print(&_mc); + let fee_amount = ptb.command( + ptb::move_call( + "@mysten/kiosk", + "royalty_rule", + "fee_amount", + vector[ptb::ext_input("policy"), paid], + vector[nft_type], + ), + ); + + let royalty_payment = ptb.command(ptb::split_coins(ptb::gas(), vector[fee_amount])); + + // pay royalty + ptb.command( + ptb::move_call( + "@mysten/kiosk", + "royalty_rule", + "pay", + vector[ + ptb::ext_input("policy"), + ptb::ext_input("request"), + royalty_payment, + ], + vector[nft_type], + ), + ); + + // lock item in the buyer kiosk + ptb.command( + ptb::move_call( + @0x2.to_string(), + "kiosk", + "lock", + vector[ptb::ext_input("buyer_kiosk"), ptb::ext_input("item")], + vector[nft_type], + ), + ); + + // prove that the item is locked + ptb.command( + ptb::move_call( + "@mysten/kiosk", + "kiosk_lock_rule", + "prove", + vector[ptb::ext_input("request"), ptb::ext_input("buyer_kiosk")], + vector[nft_type], + ), + ); + + // confirm the request + ptb.command( + ptb::move_call( + @0x2.to_string(), + "transfer_policy", + "confirm_request", + vector[ptb::ext_input("policy"), ptb::ext_input("request")], + vector[nft_type], + ), + ); } diff --git a/packages/testing/demo_usd/Move.lock b/packages/testing/demo_usd/Move.lock deleted file mode 100644 index 2054416..0000000 --- a/packages/testing/demo_usd/Move.lock +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by move; do not edit -# This file should be checked in. - -[move] -version = 4 - -[pinned.testnet.MoveStdlib] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "868c226359ef914f1f3b080518f27eb13d8967f5" } -use_environment = "testnet" -manifest_digest = "C4FE4C91DE74CBF223B2E380AE40F592177D21870DC2D7EB6227D2D694E05363" -deps = {} - -[pinned.testnet.Sui] -source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "868c226359ef914f1f3b080518f27eb13d8967f5" } -use_environment = "testnet" -manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C" -deps = { MoveStdlib = "MoveStdlib" } - -[pinned.testnet.demo_usd] -source = { root = true } -use_environment = "testnet" -manifest_digest = "F42F13A0483640C91598349E0E0C125899BD5635A850639255F6EC2E598ADAF8" -deps = { pas = "pas", ptb = "ptb", std = "MoveStdlib", sui = "Sui" } - -[pinned.testnet.pas] -source = { local = "../../pas" } -use_environment = "testnet" -manifest_digest = "38AA62656ABE7551C444DA427ADBAA7751CB67250663D39FCDE36E938138EA7D" -deps = { ptb = "ptb", std = "MoveStdlib", sui = "Sui" } - -[pinned.testnet.ptb] -source = { local = "../../ptb" } -use_environment = "testnet" -manifest_digest = "5745706258F61D6CE210904B3E6AE87A73CE9D31A6F93BE4718C442529332A87" -deps = { std = "MoveStdlib", sui = "Sui" } diff --git a/packages/testing/demo_usd/Move.toml b/packages/testing/demo_usd/Move.toml deleted file mode 100644 index f727418..0000000 --- a/packages/testing/demo_usd/Move.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "demo_usd" -edition = "2024.beta" - -[dependencies] -pas = { local = "../../pas" } -ptb = { local = "../../ptb" } diff --git a/sdk/pas/README.md b/sdk/pas/README.md index a21f4ce..0944bfb 100644 --- a/sdk/pas/README.md +++ b/sdk/pas/README.md @@ -1 +1 @@ -# Permissioned Assets Standard sdk +# Permissioned Assets Standard SDK diff --git a/sdk/pas/src/contracts/ptb/ptb.ts b/sdk/pas/src/contracts/ptb/ptb.ts index 3821768..5d17d78 100644 --- a/sdk/pas/src/contracts/ptb/ptb.ts +++ b/sdk/pas/src/contracts/ptb/ptb.ts @@ -87,7 +87,13 @@ export const CallArg = new MoveEnum({ * Extended arguments for off-chain resolution. Can be created and registered in a * transaction through `ext_input`. */ - Ext: bcs.string(), + Ext: new MoveStruct({ + name: `CallArg.Ext`, + fields: { + namespace: bcs.string(), + value: bcs.string(), + }, + }), }, }); export const Command = new MoveTuple({ diff --git a/sdk/pas/src/resolution.ts b/sdk/pas/src/resolution.ts index 7fa75f8..52ce10f 100644 --- a/sdk/pas/src/resolution.ts +++ b/sdk/pas/src/resolution.ts @@ -221,22 +221,25 @@ export function buildMoveCallCommandFromTemplate( }); } -function resolveRawPasRequest(args: RawCommandBuildArgs, value: string): Argument { - switch (value) { - case 'pas:request': +function resolveRawPasRequest(args: RawCommandBuildArgs, extInput: { _namespace: string, value: string }): Argument { + // do the logic on `namespace` here + // return error if it's not PAS + + switch (extInput.value) { + case 'request': if (!args.request) throw new PASClientError(`Request is not set in the context.`); return args.request; - case 'pas:policy': + case 'policy': if (!args.policy) throw new PASClientError(`Policy is not set in the context.`); return args.policy; - case 'pas:sender_chest': + case 'sender_chest': if (!args.senderChest) throw new PASClientError(`Sender chest is not set in the context.`); return args.senderChest; - case 'pas:receiver_chest': + case 'receiver_chest': if (!args.receiverChest) throw new PASClientError(`Receiver chest is not set in the context.`); return args.receiverChest; default: - throw new PASClientError(`Unknown pas request: ${value}`); + throw new PASClientError(`Unknown pas request: ${extInput.value}`); } } diff --git a/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move b/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move index f731906..6fdef81 100644 --- a/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move +++ b/sdk/pas/test/e2e/data/demo_usd/sources/demo_usd.move @@ -12,7 +12,7 @@ use pas::namespace::Namespace; use pas::policy::{Self, Policy, PolicyCap}; use pas::request::Request; use pas::send_funds::SendFunds; -use pas::templates::Templates; +use pas::templates::{PAS, Templates}; use ptb::ptb; use std::type_name; use sui::balance::Balance; @@ -97,7 +97,7 @@ entry fun setup(namespace: &mut Namespace, templates: &mut Templates, faucet: &m type_name.address_string().to_string(), "demo_usd", "approve_transfer", - vector[ptb::ext_input("pas:request"), ptb::object_by_id(@0x6.to_id())], + vector[ptb::ext_input("request"), ptb::object_by_id(@0x6.to_id())], vector[(*type_name.as_string()).to_string()], ); @@ -115,7 +115,7 @@ public fun use_v2( type_name::with_defining_ids().address_string().to_string(), "demo_usd", "approve_transfer_v2", - vector[ptb::ext_input("pas:request"), ptb::object_by_id(object::id(faucet))], + vector[ptb::ext_input("request"), ptb::object_by_id(object::id(faucet))], vector[], );