From 0db1e9957ac1437de9a9b864a00038dc79779ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abor?= Date: Mon, 24 Apr 2023 09:23:34 +0200 Subject: [PATCH 1/3] Standardize withdraw and deposit metadata --- contracts/allowlist/sources/allowlist.move | 13 ++ sources/kiosk/ob_kiosk.move | 118 +++++++++-------- sources/request/rule_deposit.move | 119 ++++++++++++++++++ sources/request/rule_withdraw.move | 96 ++++++++++++++ .../transfer_allowlist.move | 80 +++++++----- sources/trading/bidding.move | 8 +- sources/trading/orderbook.move | 2 - 7 files changed, 351 insertions(+), 85 deletions(-) create mode 100644 sources/request/rule_deposit.move create mode 100644 sources/request/rule_withdraw.move diff --git a/contracts/allowlist/sources/allowlist.move b/contracts/allowlist/sources/allowlist.move index afe92768..fbec5163 100644 --- a/contracts/allowlist/sources/allowlist.move +++ b/contracts/allowlist/sources/allowlist.move @@ -21,4 +21,17 @@ module allowlist::allowlist { transfer::public_transfer(al_cap, tx_context::sender(ctx)); transfer::public_share_object(al); } + + #[test_only] + use sui::test_scenario::{Self, ctx}; + + #[test] + fun test_peer_to_peer_flow() { + + } + + #[test] + fun test_bidding_flow() { + + } } diff --git a/sources/kiosk/ob_kiosk.move b/sources/kiosk/ob_kiosk.move index faad449c..def26942 100644 --- a/sources/kiosk/ob_kiosk.move +++ b/sources/kiosk/ob_kiosk.move @@ -32,12 +32,16 @@ module nft_protocol::ob_kiosk { use nft_protocol::ob_transfer_request::{Self, TransferRequest}; use nft_protocol::withdraw_request::{Self, WithdrawRequest}; use nft_protocol::borrow_request::{Self, BorrowRequest, BORROW_REQUEST}; - use nft_protocol::request::{Self, Policy, RequestBody, WithNft}; + use nft_protocol::request::{Policy, RequestBody, WithNft}; use nft_protocol::utils; use originmate::typed_id::{Self, TypedID}; + use nft_protocol::rule_deposit; + use nft_protocol::rule_withdraw; + use std::option::Option; use std::string::utf8; use std::type_name::{Self, TypeName}; + use sui::display; use sui::dynamic_field::{Self as df}; use sui::kiosk::{Self, Kiosk, KioskOwnerCap, uid_mut as ext}; @@ -140,8 +144,6 @@ module nft_protocol::ob_kiosk { struct KioskOwnerCapDfKey has store, copy, drop {} /// For `Kiosk::id` value `DepositSetting` struct DepositSettingDfKey has store, copy, drop {} - /// For `TransferRequest::metadata` value `TypeName` - struct AuthTransferRequestDfKey has store, copy, drop {} // === Instantiators === @@ -222,11 +224,16 @@ module nft_protocol::ob_kiosk { // === Deposit to the Kiosk === - /// Always works if the sender is the owner. - /// Fails if permissionless deposits are not enabled for `T`. - /// See `DepositSetting`. + /// Deposit NFT into `Kiosk` + /// + /// #### Panics + /// + /// Panics if permissionless deposits are not enabled for `T` and + /// transaction sender is not owner, see `DepositSetting`. public fun deposit( - self: &mut Kiosk, nft: T, ctx: &mut TxContext, + self: &mut Kiosk, + nft: T, + ctx: &mut TxContext, ) { assert_can_deposit(self, ctx); @@ -245,6 +252,27 @@ module nft_protocol::ob_kiosk { set_cap(self, cap); } + /// Deposit NFT into `Kiosk` in the context of a protected transfer + /// + /// Registers `DepositRule` metadata on transfer request. + /// + /// #### Panics + /// + /// Panics if permissionless deposits are not enabled for `T` and + /// transaction sender is not owner, see `DepositSetting`. + public fun deposit_transfer( + req: &mut RequestBody>, + self: &mut Kiosk, + nft: T, + ctx: &mut TxContext, + ) { + let deposit_rule = rule_deposit::new( + Witness {}, &nft, object::id_address(self), + ); + rule_deposit::register_metadata(req, deposit_rule); + deposit(self, nft, ctx) + } + // === Withdraw from the Kiosk === /// Authorizes given entity to take given NFT out. @@ -312,9 +340,14 @@ module nft_protocol::ob_kiosk { entity_id: &UID, ctx: &mut TxContext, ): TransferRequest { - let (nft, builder) = transfer_nft_(source, nft_id, uid_to_address(entity_id), ctx); - deposit(target, nft, ctx); - builder + let (nft, req) = transfer_nft_(source, nft_id, uid_to_address(entity_id), ctx); + deposit_transfer( + ob_transfer_request::inner_mut(&mut req), + target, + nft, + ctx, + ); + req } /// Similar to `transfer_delegated` but instead of proving origin with @@ -327,9 +360,14 @@ module nft_protocol::ob_kiosk { nft_id: ID, ctx: &mut TxContext, ): TransferRequest { - let (nft, builder) = transfer_nft_(source, nft_id, sender(ctx), ctx); - deposit(target, nft, ctx); - builder + let (nft, req) = transfer_nft_(source, nft_id, sender(ctx), ctx); + deposit_transfer( + ob_transfer_request::inner_mut(&mut req), + target, + nft, + ctx, + ); + req } /// We allow withdrawing NFTs for some use cases. @@ -469,8 +507,16 @@ module nft_protocol::ob_kiosk { ctx: &mut TxContext, ): (T, TransferRequest) { let nft = get_nft(self, nft_id, originator); + let req = ob_transfer_request::new(nft_id, originator, ctx); - (nft, ob_transfer_request::new(nft_id, originator, ctx)) + let withdraw_rule = rule_withdraw::new( + Witness {}, &nft, object::id_address(self), + ); + rule_withdraw::register_metadata( + ob_transfer_request::inner_mut(&mut req), withdraw_rule, + ); + + (nft, req) } /// After authorization that the call is permitted, gets the NFT. @@ -481,8 +527,16 @@ module nft_protocol::ob_kiosk { ctx: &mut TxContext, ): (T, WithdrawRequest) { let nft = get_nft(self, nft_id, originator); + let req = withdraw_request::new(originator, ctx); - (nft, withdraw_request::new(originator, ctx)) + let withdraw_rule = rule_withdraw::new( + Witness {}, &nft, object::id_address(self), + ); + rule_withdraw::register_metadata( + withdraw_request::inner_mut(&mut req), withdraw_rule, + ); + + (nft, req) } fun get_nft( @@ -501,40 +555,6 @@ module nft_protocol::ob_kiosk { // === Request Auth === - /// Proves access to given type `Auth`. - /// Useful in conjunction with witness-like types. - /// Trading contracts proves themselves with `Auth` instead of UID. - /// This makes it easier to implement allowlists since we can globally - /// allow a contract to trade. - /// Allowlist could also be implemented with a UID but that would require - /// that the trading contracts maintain a global object. - /// In some cases this is doable, in other it's inconvenient. - public fun set_transfer_request_auth( - req: &mut TransferRequest, auth: &Auth, - ) { - set_transfer_request_auth_(ob_transfer_request::inner_mut(req), auth) - } - - public fun set_transfer_request_auth_( - req: &mut RequestBody>, _auth: &Auth, - ) { - let metadata = request::metadata_mut(req); - df::add(metadata, AuthTransferRequestDfKey {}, type_name::get()); - } - - /// What's the authority that created this request? - public fun get_transfer_request_auth(req: &TransferRequest): &TypeName { - get_transfer_request_auth_(ob_transfer_request::inner(req)) - } - - /// What's the authority that created this request? - public fun get_transfer_request_auth_( - req: &RequestBody>, - ): &TypeName { - let metadata = request::metadata(req); - df::borrow(metadata, AuthTransferRequestDfKey {}) - } - // === De-listing of NFTs === /// Removes _all_ entities from access to the NFT. diff --git a/sources/request/rule_deposit.move b/sources/request/rule_deposit.move new file mode 100644 index 00000000..55a919b2 --- /dev/null +++ b/sources/request/rule_deposit.move @@ -0,0 +1,119 @@ +module nft_protocol::rule_deposit { + use std::type_name::{Self, TypeName}; + + use sui::object::{Self, UID, ID}; + use sui::dynamic_field as df; + + use nft_protocol::request::{Self, RequestBody, WithNft}; + use nft_protocol::rule_withdraw::{Self, WithdrawRule}; + + /// `DepositRule` metadata was not registered on `TransferRequest` + const EUndefinedMetadata: u64 = 1; + + /// `DepositRule` metadata was already registered on `TransferRequest` + const EExistingMetadata: u64 = 2; + + /// `WithdrawRule` metadata did not match the `DepositRule` metadata + const EInvalidWithdrawMetadata: u64 = 3; + + struct DepositRule has drop, store { + /// NFT `ID` that was deposited + nft_id: ID, + /// Type of NFT that was deposited + nft_type: TypeName, + /// Address from which the NFT was deposited + /// + /// This can be an object `ID` converted into an `address` or a user pubkey. + source: address, + /// `TypeName` of authority that withdrew the NFT + /// + /// This must be an authority authorized to perform transactions on + /// NFTs of `nft_type` by an `Allowlist`. + authority: TypeName, + } + + struct DepositKey has copy, drop, store {} + + public fun new( + _auth: Auth, + nft: &T, + source: address, + ): DepositRule { + DepositRule { + nft_id: object::id(nft), + nft_type: type_name::get(), + source, + authority: type_name::get(), + } + } + + public fun borrow_nft_id(rule: &DepositRule): &ID { + &rule.nft_id + } + + public fun borrow_nft_type(rule: &DepositRule): &TypeName { + &rule.nft_type + } + + public fun borrow_source(rule: &DepositRule): &address { + &rule.source + } + + public fun borrow_authority(rule: &DepositRule): &TypeName { + &rule.authority + } + + // === Transfers === + + /// Register `DepositRule` metadata on request + public fun register_metadata( + req: &mut RequestBody>, + rule: DepositRule, + ) { + let metadata = request::metadata_mut(req); + add_metadata(metadata, rule) + } + + /// Check whether `DepositRule` metadata is registered on object + public fun contains_metadata(metadata: &UID): bool { + df::exists_(metadata, DepositKey {}) + } + + /// Register `DepositRule` metadata on object + public fun add_metadata(metadata: &mut UID, rule: DepositRule) { + assert!(!contains_metadata(metadata), EExistingMetadata); + df::add(metadata, DepositKey {}, rule) + } + + /// Borrow `DepositRule` metadata from object + public fun borrow_metadata(metadata: &UID): &DepositRule { + assert_metadata(metadata); + df::borrow(metadata, DepositKey {}) + } + + // === Assertions === + + /// Assert matching `WithdrawRule` + /// + /// #### Panics + /// + /// Panics if matching `WithdrawRule` is not registered as metadata. + public fun assert_matching_withdrawal( + withdraw_rule: &WithdrawRule, + rule: &DepositRule, + ) { + assert!( + rule_withdraw::borrow_nft_id(withdraw_rule) == &rule.nft_id, + EInvalidWithdrawMetadata, + ); + assert!( + rule_withdraw::borrow_nft_type(withdraw_rule) == &rule.nft_type, + EInvalidWithdrawMetadata, + ); + } + + /// Assert `DepositRule` is registered on object + public fun assert_metadata(metadata: &UID) { + assert!(contains_metadata(metadata), EUndefinedMetadata) + } +} diff --git a/sources/request/rule_withdraw.move b/sources/request/rule_withdraw.move new file mode 100644 index 00000000..07e32239 --- /dev/null +++ b/sources/request/rule_withdraw.move @@ -0,0 +1,96 @@ +module nft_protocol::rule_withdraw { + use std::type_name::{Self, TypeName}; + + use sui::object::{Self, UID, ID}; + use sui::dynamic_field as df; + + use nft_protocol::request::{Self, RequestBody, WithNft}; + + /// `WithdrawRule` metadata was not registered on `TransferRequest` + const EUndefinedMetadata: u64 = 1; + + /// `WithdrawRule` metadata was already registered on `TransferRequest` + const EExistingMetadata: u64 = 2; + + struct WithdrawRule has drop, store { + /// NFT `ID` that was withdrawn + nft_id: ID, + /// Type of NFT that was withdrawn + nft_type: TypeName, + /// Address from which the NFT was withdrawn + /// + /// This can be an object `ID` converted into an `address` or a user pubkey. + source: address, + /// `TypeName` of authority that withdrew the NFT + /// + /// This must be an authority authorized to perform transactions on + /// NFTs of `nft_type` by an `Allowlist`. + authority: TypeName, + } + + struct WithdrawKey has copy, drop, store {} + + public fun new( + _auth: Auth, + nft: &T, + source: address, + ): WithdrawRule { + WithdrawRule { + nft_id: object::id(nft), + nft_type: type_name::get(), + source, + authority: type_name::get(), + } + } + + public fun borrow_nft_id(rule: &WithdrawRule): &ID { + &rule.nft_id + } + + public fun borrow_nft_type(rule: &WithdrawRule): &TypeName { + &rule.nft_type + } + + public fun borrow_source(rule: &WithdrawRule): &address { + &rule.source + } + + public fun borrow_authority(rule: &WithdrawRule): &TypeName { + &rule.authority + } + + // === Transfers === + + /// Register `WithdrawRule` metadata on request + public fun register_metadata( + req: &mut RequestBody>, + rule: WithdrawRule, + ) { + let metadata = request::metadata_mut(req); + add_metadata(metadata, rule) + } + + /// Check whether `WithdrawRule` metadata is registered on object + public fun contains_metadata(metadata: &UID): bool { + df::exists_(metadata, WithdrawKey {}) + } + + /// Register `WithdrawRule` metadata on object + public fun add_metadata(metadata: &mut UID, rule: WithdrawRule) { + assert!(!contains_metadata(metadata), EExistingMetadata); + df::add(metadata, WithdrawKey {}, rule) + } + + /// Borrow `WithdrawRule` metadata from object + public fun borrow_metadata(metadata: &UID): &WithdrawRule { + assert_metadata(metadata); + df::borrow(metadata, WithdrawKey {}) + } + + // === Assertions === + + /// Assert `WithdrawRule` is registered on object + public fun assert_metadata(metadata: &UID) { + assert!(contains_metadata(metadata), EUndefinedMetadata) + } +} diff --git a/sources/standards/transfer_allowlist/transfer_allowlist.move b/sources/standards/transfer_allowlist/transfer_allowlist.move index c507736e..a86e7736 100644 --- a/sources/standards/transfer_allowlist/transfer_allowlist.move +++ b/sources/standards/transfer_allowlist/transfer_allowlist.move @@ -28,9 +28,9 @@ module nft_protocol::transfer_allowlist { use sui::dynamic_field as df; use nft_protocol::request::{Self, RequestBody, Policy, PolicyCap, WithNft}; - use nft_protocol::ob_kiosk; use nft_protocol::ob_transfer_request::{Self, TransferRequest}; use nft_protocol::witness::Witness as DelegatedWitness; + use nft_protocol::rule_deposit; use std::option::{Self, Option}; use std::string::utf8; @@ -99,7 +99,7 @@ module nft_protocol::transfer_allowlist { /// /// That's because the sui implementation of `TransferRequest` is simplified /// and does not support safe metadata about the originator of the transfer. - struct AllowlistRule has drop {} + struct AllowlistReceipt has drop {} /// Creates a new `Allowlist` public fun new(ctx: &mut TxContext): (Allowlist, AllowlistOwnerCap) { @@ -346,54 +346,66 @@ module nft_protocol::transfer_allowlist { // === Transfers === - /// Checks whether given authority witness is in the allowlist, and also - /// whether given collection witness (T) is in the allowlist. - public fun can_be_transferred( - self: &Allowlist, - auth: &TypeName, - collection: TypeName, - ): bool { - contains_authority(self, auth) && - contains_collection(self, collection) - } - - /// Registers collection to use `Allowlist` during the transfer. + /// Use `Allowlist` to validate authorities during transfer + /// + /// `DepositRule` authority will be validated public fun enforce( policy: &mut Policy>, cap: &PolicyCap, ) { - request::enforce_rule_no_state, AllowlistRule>(policy, cap); + request::enforce_rule_no_state, AllowlistReceipt>(policy, cap); } + /// Don't use `Allowlist` to validate authorities during transfer + /// + /// `DepositRule` authority will be validated public fun drop( policy: &mut Policy>, cap: &PolicyCap, ) { - request::drop_rule_no_state, AllowlistRule>(policy, cap); + request::drop_rule_no_state, AllowlistReceipt>(policy, cap); } - /// Confirms that the transfer is allowed by the `Allowlist`. - /// It adds a signature to the request. - /// In the end, if the allowlist rule is included in the transfer policy, - /// the transfer request can only be finished if this rule is present. + /// Confirms that the transfer is allowed by the `Allowlist` + /// + /// Asserts that `DepositRule` is present as metadata on request. Ensures + /// that for any arbitrary transaction the NFT has ended up in a trusted + /// location. + /// + /// #### Panics + /// + /// * `DepositRule` is not present as metadata on request + /// * `DepositRule` NFT type or authority is not valid for `Allowlist` public fun confirm_transfer( - self: &Allowlist, req: &mut TransferRequest, + self: &Allowlist, ) { - confirm_transfer_(self, ob_transfer_request::inner_mut(req)) + confirm_transfer_(ob_transfer_request::inner_mut(req), self) } - /// Confirms that the transfer is allowed by the `Allowlist`. - /// It adds a signature to the request. - /// In the end, if the allowlist rule is included in the transfer policy, - /// the transfer request can only be finished if this rule is present. + /// Confirms that the transfer is allowed by the `Allowlist` + /// + /// Asserts that `DepositRule` is present as metadata on request. Ensures + /// that for any arbitrary transaction the NFT has ended up in a trusted + /// location. + /// + /// #### Panics + /// + /// * `DepositRule` is not present as metadata on request + /// * `DepositRule` NFT type or authority is not valid for `Allowlist` public fun confirm_transfer_( - self: &Allowlist, req: &mut RequestBody>, + self: &Allowlist, ) { - let auth = ob_kiosk::get_transfer_request_auth_(req); - assert_transferable(self, auth); - request::add_receipt(req, &AllowlistRule {}); + let metadata = request::metadata(req); + let deposit_rule = rule_deposit::borrow_metadata(metadata); + + assert_transferable( + self, + *rule_deposit::borrow_nft_type(deposit_rule), + rule_deposit::borrow_authority(deposit_rule), + ); + request::add_receipt(req, &AllowlistReceipt {}); } // === Assertions === @@ -459,8 +471,12 @@ module nft_protocol::transfer_allowlist { /// /// Panics if neither `T` is not transferrable or `Auth` is not a /// valid authority. - public fun assert_transferable(allowlist: &Allowlist, auth: &TypeName) { - assert_collection(allowlist, type_name::get()); + public fun assert_transferable( + allowlist: &Allowlist, + nft_type: TypeName, + auth: &TypeName, + ) { + assert_collection(allowlist, nft_type); assert_authority(allowlist, auth); } diff --git a/sources/trading/bidding.move b/sources/trading/bidding.move index 5c6e2472..2afbfba5 100644 --- a/sources/trading/bidding.move +++ b/sources/trading/bidding.move @@ -171,12 +171,17 @@ module nft_protocol::bidding { ctx: &mut TxContext, ): TransferRequest { let nft_id = object::id(&nft); - ob_kiosk::deposit(buyers_kiosk, nft, ctx); let transfer_req = ob_transfer_request::new( nft_id, uid_to_address(&bid.id), ctx, ); + ob_kiosk::deposit_transfer( + ob_transfer_request::inner_mut(&mut transfer_req), + buyers_kiosk, + nft, + ctx, + ); sell_nft_common(bid, buyers_kiosk, transfer_req, nft_id, ctx) } @@ -256,7 +261,6 @@ module nft_protocol::bidding { ob_transfer_request::set_paid( &mut transfer_req, balance::withdraw_all(&mut bid.offer), seller, ); - ob_kiosk::set_transfer_request_auth(&mut transfer_req, &Witness {}); trading::transfer_bid_commission(&mut bid.commission, ctx); diff --git a/sources/trading/orderbook.move b/sources/trading/orderbook.move index b082ad0b..ecb48085 100644 --- a/sources/trading/orderbook.move +++ b/sources/trading/orderbook.move @@ -1191,7 +1191,6 @@ module nft_protocol::orderbook { ctx, ); ob_transfer_request::set_paid(&mut transfer_req, bid_offer, seller); - ob_kiosk::set_transfer_request_auth(&mut transfer_req, &Witness {}); transfer_req } @@ -1230,7 +1229,6 @@ module nft_protocol::orderbook { ob_transfer_request::set_paid( &mut transfer_req, balance::withdraw_all(paid), *seller, ); - ob_kiosk::set_transfer_request_auth(&mut transfer_req, &Witness {}); transfer_req } From 170f6aa48066b4278f4cfbd1e167efabbb469f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abor?= Date: Mon, 24 Apr 2023 11:13:02 +0200 Subject: [PATCH 2/3] Establish P2P transfer rule --- .../examples/sources/testract.move | 2 +- sources/kiosk/policies/borrow.move | 5 +- .../kiosk/policies/ob_transfer_request.move | 5 +- sources/kiosk/policies/withdraw/withdraw.move | 5 +- sources/permissions/access_policy.move | 2 +- sources/permissions/authlist.move | 5 ++ sources/permissions/session_tokens.move | 2 +- sources/request/request.move | 5 +- sources/request/rule_deposit.move | 2 +- sources/request/rule_peer.move | 90 +++++++++++++++++++ .../standards/royalties/royalty_strategy.move | 4 +- .../transfer_allowlist.move | 59 +++++++++--- 12 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 sources/permissions/authlist.move create mode 100644 sources/request/rule_peer.move diff --git a/example_collections/examples/sources/testract.move b/example_collections/examples/sources/testract.move index b12c2972..792ccca2 100644 --- a/example_collections/examples/sources/testract.move +++ b/example_collections/examples/sources/testract.move @@ -302,7 +302,7 @@ module examples::testract { // and now for confirming transfer // (see `register_allowlist_and_royalty_strategy` and `create_allowlist`) - transfer_allowlist::confirm_transfer(allowlist, &mut transfer_req); + transfer_allowlist::confirm_transfer(&mut transfer_req, allowlist); royalty_strategy_bps::confirm_transfer(royalty_strategy, &mut transfer_req); // only if both rules are OK can we destroy the hot potato diff --git a/sources/kiosk/policies/borrow.move b/sources/kiosk/policies/borrow.move index 7d410977..d3b7ae1b 100644 --- a/sources/kiosk/policies/borrow.move +++ b/sources/kiosk/policies/borrow.move @@ -61,7 +61,10 @@ module nft_protocol::borrow_request { /// Adds a `Receipt` to the `Request`, unblocking the request and /// confirming that the policy requirements are satisfied. - public fun add_receipt(self: &mut BorrowRequest, rule: &Rule) { + public fun add_receipt( + self: &mut BorrowRequest, + rule: Rule, + ) { request::add_receipt(&mut self.inner, rule); } diff --git a/sources/kiosk/policies/ob_transfer_request.move b/sources/kiosk/policies/ob_transfer_request.move index c3bf0487..2e7ab066 100644 --- a/sources/kiosk/policies/ob_transfer_request.move +++ b/sources/kiosk/policies/ob_transfer_request.move @@ -165,7 +165,10 @@ module nft_protocol::ob_transfer_request { /// Adds a `Receipt` to the `TransferRequest`, unblocking the request and /// confirming that the policy requirements are satisfied. - public fun add_receipt(self: &mut TransferRequest, rule: &Rule) { + public fun add_receipt( + self: &mut TransferRequest, + rule: Rule, + ) { request::add_receipt(&mut self.inner, rule); } diff --git a/sources/kiosk/policies/withdraw/withdraw.move b/sources/kiosk/policies/withdraw/withdraw.move index 89306f95..a644d15a 100644 --- a/sources/kiosk/policies/withdraw/withdraw.move +++ b/sources/kiosk/policies/withdraw/withdraw.move @@ -36,7 +36,10 @@ module nft_protocol::withdraw_request { /// Adds a `Receipt` to the `Request`, unblocking the request and /// confirming that the policy requirements are satisfied. - public fun add_receipt(self: &mut WithdrawRequest, rule: &Rule) { + public fun add_receipt( + self: &mut WithdrawRequest, + rule: Rule, + ) { request::add_receipt(&mut self.inner, rule); } diff --git a/sources/permissions/access_policy.move b/sources/permissions/access_policy.move index 924056ca..a95d62dd 100644 --- a/sources/permissions/access_policy.move +++ b/sources/permissions/access_policy.move @@ -123,7 +123,7 @@ module nft_protocol::access_policy { assert_parent_auth(self, ctx); }; - borrow_request::add_receipt(req, &AccessPolicyRule {}); + borrow_request::add_receipt(req, AccessPolicyRule {}); } public fun confirm_from_collection( diff --git a/sources/permissions/authlist.move b/sources/permissions/authlist.move new file mode 100644 index 00000000..c7599636 --- /dev/null +++ b/sources/permissions/authlist.move @@ -0,0 +1,5 @@ +module nft_protocol::authlist { + struct Authlist { + // TODO: Exact same API as `Allowlist` but for pub keys + } +} diff --git a/sources/permissions/session_tokens.move b/sources/permissions/session_tokens.move index 19c83817..8fc97881 100644 --- a/sources/permissions/session_tokens.move +++ b/sources/permissions/session_tokens.move @@ -132,7 +132,7 @@ module nft_protocol::session_token { assert_parent_auth(self, req); }; - borrow_request::add_receipt(req, &SessionTokenRule {}); + borrow_request::add_receipt(req, SessionTokenRule {}); } public fun assert_field_auth( diff --git a/sources/request/request.move b/sources/request/request.move index eda2e9c9..82bb333b 100644 --- a/sources/request/request.move +++ b/sources/request/request.move @@ -99,7 +99,10 @@ module nft_protocol::request { /// Adds a `Receipt` to the `RequestBody`, unblocking the request and /// confirming that the policy RequestBodys are satisfied. - public fun add_receipt(self: &mut RequestBody

, _rule: &Rule) { + public fun add_receipt( + self: &mut RequestBody

, + _rule: Rule, + ) { vec_set::insert(&mut self.receipts, type_name::get()) } diff --git a/sources/request/rule_deposit.move b/sources/request/rule_deposit.move index 55a919b2..63da97c0 100644 --- a/sources/request/rule_deposit.move +++ b/sources/request/rule_deposit.move @@ -98,7 +98,7 @@ module nft_protocol::rule_deposit { /// #### Panics /// /// Panics if matching `WithdrawRule` is not registered as metadata. - public fun assert_matching_withdrawal( + public fun assert_matching_withdrawal( withdraw_rule: &WithdrawRule, rule: &DepositRule, ) { diff --git a/sources/request/rule_peer.move b/sources/request/rule_peer.move new file mode 100644 index 00000000..0a455ca2 --- /dev/null +++ b/sources/request/rule_peer.move @@ -0,0 +1,90 @@ +/// Module implements peer to peer transactions via `Authlist` +module nft_protocol::rule_peer { + use nft_protocol::transfer_allowlist; + use nft_protocol::request::{Self, RequestBody, Policy, PolicyCap, WithNft}; + use nft_protocol::ob_transfer_request::{Self, TransferRequest}; + use nft_protocol::authlist::Authlist; + use nft_protocol::rule_deposit; + use nft_protocol::rule_withdraw; + + struct PeerReceipt has copy, drop, store {} + + // === Transfers === + + /// Use `Allowlist` to validate authorities during transfer + /// + /// `DepositRule` authority will be validated + public fun enforce( + policy: &mut Policy>, + cap: &PolicyCap, + ) { + request::enforce_rule_no_state, PeerReceipt>(policy, cap); + } + + /// Don't use `Allowlist` to validate authorities during transfer + /// + /// `DepositRule` authority will be validated + public fun drop( + policy: &mut Policy>, + cap: &PolicyCap, + ) { + request::drop_rule_no_state, PeerReceipt>(policy, cap); + } + + /// Authorize transfer between source and destination given that a pubkey + /// authority can determine that both locations are eligible for peer + /// transfers. + /// + /// #### Panics + /// + /// * `AllowlistReceipt` was not issued + public fun confirm_transfer( + req: &mut TransferRequest, + self: &Authlist, + pub_key: vector, + signature: vector, + ) { + confirm_transfer_( + ob_transfer_request::inner_mut(req), + self, + pub_key, + signature, + ) + } + + /// Authorize transfer between source and destination given that a pubkey + /// authority can determine that both locations are eligible for peer + /// transfers. + /// + /// #### Panics + /// + /// * `AllowlistReceipt` was not issued + public fun confirm_transfer_( + req: &mut RequestBody>, + _self: &Authlist, + _pub_key: vector, + _signature: vector, + ) { + let metadata = request::metadata(req); + + // Verifies that transaction has true source and destination + transfer_allowlist::assert_allowlist_receipt(req); + + // If `AllowlistReceipt` was issued then `WithdrawRule` and + // `DepositRule` exist. + let withdraw_rule = rule_withdraw::borrow_metadata(metadata); + let deposit_rule = rule_deposit::borrow_metadata(metadata); + + // Assert that we have withdrawn and deposited the same NFT + rule_deposit::assert_matching_withdrawal(withdraw_rule, deposit_rule); + + // TODO: `signature` should contain a signed digest of source and + // destination addresses, signed by one of the pubkeys in `Authlist` + // + // `sign(source_address | destination_address)` + // + // Assert this... + + request::add_receipt(req, PeerReceipt {}); + } +} diff --git a/sources/standards/royalties/royalty_strategy.move b/sources/standards/royalties/royalty_strategy.move index a1ca0bf6..415ce8d1 100644 --- a/sources/standards/royalties/royalty_strategy.move +++ b/sources/standards/royalties/royalty_strategy.move @@ -122,7 +122,7 @@ module nft_protocol::royalty_strategy_bps { let fee_amount = calculate(self, balance::value(paid)); balances::take_from(&mut self.aggregator, paid, fee_amount); - ob_transfer_request::add_receipt(req, &BpsRoyaltyStrategyRule {}); + ob_transfer_request::add_receipt(req, BpsRoyaltyStrategyRule {}); } /// Instead of using the balance associated with the `TransferRequest`, @@ -138,7 +138,7 @@ module nft_protocol::royalty_strategy_bps { let fee_amount = calculate(self, paid); balances::take_from(&mut self.aggregator, wallet, fee_amount); - ob_transfer_request::add_receipt(req, &BpsRoyaltyStrategyRule {}); + ob_transfer_request::add_receipt(req, BpsRoyaltyStrategyRule {}); } public fun royalty_fee_bps(self: &BpsRoyaltyStrategy): u16 { diff --git a/sources/standards/transfer_allowlist/transfer_allowlist.move b/sources/standards/transfer_allowlist/transfer_allowlist.move index a86e7736..15e9e9fa 100644 --- a/sources/standards/transfer_allowlist/transfer_allowlist.move +++ b/sources/standards/transfer_allowlist/transfer_allowlist.move @@ -31,6 +31,7 @@ module nft_protocol::transfer_allowlist { use nft_protocol::ob_transfer_request::{Self, TransferRequest}; use nft_protocol::witness::Witness as DelegatedWitness; use nft_protocol::rule_deposit; + use nft_protocol::rule_withdraw; use std::option::{Self, Option}; use std::string::utf8; @@ -62,6 +63,9 @@ module nft_protocol::transfer_allowlist { /// Transfer authority was already registered const EExistingAuthority: u64 = 5; + /// Transfer request did not have a `AllowlistReceipt` + const EInvalidAllowlistReceipt: u64 = 6; + // === Structs === struct Allowlist has key, store { @@ -270,8 +274,11 @@ module nft_protocol::transfer_allowlist { // === Authority management === /// Returns whether `Allowlist` contains authority - public fun contains_authority(self: &Allowlist, auth: &TypeName): bool { - vec_set::contains(&self.authorities, auth) + public fun contains_authority( + self: &Allowlist, + authority: &TypeName, + ): bool { + vec_set::contains(&self.authorities, authority) } /// Insert a new authority into `Allowlist` using admin witness @@ -304,9 +311,9 @@ module nft_protocol::transfer_allowlist { /// Register authority and provide error reporting fun insert_authority_(self: &mut Allowlist) { - let collection = type_name::get(); - assert!(!contains_authority(self, &collection), EExistingAuthority); - vec_set::insert(&mut self.authorities, collection); + let authority = type_name::get(); + assert!(!contains_authority(self, &authority), EExistingAuthority); + vec_set::insert(&mut self.authorities, authority); } /// Remove authority from `Allowlist` @@ -368,12 +375,14 @@ module nft_protocol::transfer_allowlist { /// Confirms that the transfer is allowed by the `Allowlist` /// - /// Asserts that `DepositRule` is present as metadata on request. Ensures - /// that for any arbitrary transaction the NFT has ended up in a trusted - /// location. + /// Asserts that `WithdrawRule` abd `DepositRule` are present as metadata + /// on request. Ensures that for any arbitrary transaction the NFT has + /// ended up in a trusted location. /// /// #### Panics /// + /// * `WithdrawRule` is not present as metadata on request + /// * `WithdrawRule` NFT type or authority is not valid for `Allowlist` /// * `DepositRule` is not present as metadata on request /// * `DepositRule` NFT type or authority is not valid for `Allowlist` public fun confirm_transfer( @@ -385,12 +394,14 @@ module nft_protocol::transfer_allowlist { /// Confirms that the transfer is allowed by the `Allowlist` /// - /// Asserts that `DepositRule` is present as metadata on request. Ensures - /// that for any arbitrary transaction the NFT has ended up in a trusted - /// location. + /// Asserts that `WithdrawRule` abd `DepositRule` are present as metadata + /// on request. Ensures that for any arbitrary transaction the NFT has + /// ended up in a trusted location. /// /// #### Panics /// + /// * `WithdrawRule` is not present as metadata on request + /// * `WithdrawRule` NFT type or authority is not valid for `Allowlist` /// * `DepositRule` is not present as metadata on request /// * `DepositRule` NFT type or authority is not valid for `Allowlist` public fun confirm_transfer_( @@ -398,14 +409,25 @@ module nft_protocol::transfer_allowlist { self: &Allowlist, ) { let metadata = request::metadata(req); - let deposit_rule = rule_deposit::borrow_metadata(metadata); + // Verify that transaction is not lying about the source of the NFT + let withdraw_rule = rule_withdraw::borrow_metadata(metadata); + assert_transferable( + self, + *rule_withdraw::borrow_nft_type(withdraw_rule), + rule_withdraw::borrow_authority(withdraw_rule), + ); + + // Verify that the transaction is not lying about the destination of + // the NFT. + let deposit_rule = rule_deposit::borrow_metadata(metadata); assert_transferable( self, *rule_deposit::borrow_nft_type(deposit_rule), rule_deposit::borrow_authority(deposit_rule), ); - request::add_receipt(req, &AllowlistReceipt {}); + + request::add_receipt(req, AllowlistReceipt {}); } // === Assertions === @@ -480,6 +502,17 @@ module nft_protocol::transfer_allowlist { assert_authority(allowlist, auth); } + /// Asserts that transaction was verified using `Allowlist` + public fun assert_allowlist_receipt( + req: &RequestBody>, + ) { + let receipts = request::receipts(req); + assert!( + vec_set::contains(receipts, &type_name::get()), + EInvalidAllowlistReceipt, + ); + } + // === Display standard === struct TRANSFER_ALLOWLIST has drop {} From 9c68a25d4aa7ce860acd24b5423f5c31ae266139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abor?= Date: Mon, 24 Apr 2023 12:32:20 +0200 Subject: [PATCH 3/3] Rebased --- contracts/allowlist/sources/allowlist.move | 26 ++++++++++++++++++-- sources/kiosk/policies/withdraw/wallets.move | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/contracts/allowlist/sources/allowlist.move b/contracts/allowlist/sources/allowlist.move index fbec5163..74ef47a6 100644 --- a/contracts/allowlist/sources/allowlist.move +++ b/contracts/allowlist/sources/allowlist.move @@ -5,15 +5,16 @@ module allowlist::allowlist { use nft_protocol::bidding; use nft_protocol::orderbook; use nft_protocol::transfer_allowlist; + use nft_protocol::ob_kiosk; struct ALLOWLIST has drop {} fun init(_otw: ALLOWLIST, ctx: &mut TxContext) { let (al, al_cap) = transfer_allowlist::new(ctx); - // orderbooks can perform trades with our allowlist + // OB Kiosk is a trusted type for receiving NFTs + transfer_allowlist::insert_authority(&al_cap, &mut al); transfer_allowlist::insert_authority(&al_cap, &mut al); - // bidding contract can perform trades too transfer_allowlist::insert_authority(&al_cap, &mut al); // Delete `AllowlistOwnerCap` to guarantee that each release of @@ -22,12 +23,33 @@ module allowlist::allowlist { transfer::public_share_object(al); } + #[test_only] + use sui::object::{Self, UID}; #[test_only] use sui::test_scenario::{Self, ctx}; + #[test_only] + use nft_protocol::ob_transfer_request; + + #[test_only] + const USER: address = @0xA1C04; + + #[test_only] + struct Foo has key, store { + id: UID, + } + + #[test_only] + struct FOO {} + #[test] fun test_peer_to_peer_flow() { + let scenario = test_scenario::begin(USER); + + init(ALLOWLIST {}, ctx(&mut scenario)); + let (policy, cap) = + ob_transfer_request::init_policy(publisher, ctx); } #[test] diff --git a/sources/kiosk/policies/withdraw/wallets.move b/sources/kiosk/policies/withdraw/wallets.move index e590f620..377f8da7 100644 --- a/sources/kiosk/policies/withdraw/wallets.move +++ b/sources/kiosk/policies/withdraw/wallets.move @@ -93,6 +93,6 @@ module nft_protocol::wallets { ) { assert!(vec_set::contains(&self.wallets, &receiver), EUnauthorisedAddress); transfer::public_transfer(nft, receiver); - request::add_receipt(req, &WalletsRule {}); + request::add_receipt(req, WalletsRule {}); } }