From 1d6fa1546e05ca8d37bef970baaa8018f6bb2d8a Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 12:35:03 +0200 Subject: [PATCH 01/35] Draft of safe --- sources/safe/safe.move | 117 +++++++++++++++++++++++++++++++++++++++++ sources/utils/err.move | 8 ++- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 sources/safe/safe.move diff --git a/sources/safe/safe.move b/sources/safe/safe.move new file mode 100644 index 00000000..0f67e3e3 --- /dev/null +++ b/sources/safe/safe.move @@ -0,0 +1,117 @@ +module nft_protocol::safe { + use sui::object::{ID, UID}; + use sui::vec_set::{Self, VecSet}; + use sui::tx_context::{Self, TxContext}; + use sui::object; + use sui::transfer::{share_object, transfer}; + use nft_protocol::err; + + struct Safe has key { + id: UID, + nfts: VecSet, + listed_nfts: VecSet, + exclusively_listed_nfts: VecSet, + } + + struct OwnerCap has key, store { + id: UID, + safe: ID, + } + + struct DepositCap has key, store { + id: UID, + safe: ID, + } + + struct WithdrawCap has key, store { + id: UID, + safe_id: ID, + nft_id: ID, + /// If set to true, only one `WithdrawCap` can be issued for this NFT. + /// It also cannot be revoked without burning this object. + /// + /// This is useful for trading flows that cannot guarantee that the NFT + /// is claimed atomically. + is_exlusive: bool, + } + + /// Instantiates a new shared object `Safe` and transfer `OwnerCap` to the + /// tx sender. + public entry fun create_for_sender(ctx: &mut TxContext) { + transfer(create_safe_(ctx), tx_context::sender(ctx)); + } + + /// Creates a new `Safe` shared object and returns the authority capability + /// that grants authority over this safe. + public fun create_safe(ctx: &mut TxContext): OwnerCap { + create_safe_(ctx) + } + + public fun create_withdraw_cap( + nft: ID, + owner_cap: &OwnerCap, + safe: &mut Safe, + ctx: &mut TxContext, + ): WithdrawCap { + assert_owner(owner_cap, safe); + assert_in_set(&safe.nfts, &nft); + assert_not_in_set(&safe.exclusively_listed_nfts, &nft); + + WithdrawCap { + id: object::new(ctx), + safe_id: object::id(safe), + nft_id: nft, + is_exlusive: false, + } + } + public fun create_exlusive_withdraw_cap( + nft: ID, + owner_cap: &OwnerCap, + safe: &mut Safe, + ctx: &mut TxContext, + ): WithdrawCap { + let cap = create_withdraw_cap(nft, owner_cap, safe, ctx); + + assert_not_in_set(&safe.listed_nfts, &nft); + cap.is_exlusive = true; + cap + } + + fun create_safe_(ctx: &mut TxContext): OwnerCap { + let safe = Safe { + id: object::new(ctx), + nfts: vec_set::empty(), + listed_nfts: vec_set::empty(), + exclusively_listed_nfts: vec_set::empty(), + }; + let cap = OwnerCap { + id: object::new(ctx), + safe: object::id(&safe), + }; + + share_object(safe); + cap + } + + //------- getters ------- + + //------- assertions ------- + + public fun assert_owner(cap: &OwnerCap, safe: &Safe) { + assert!(cap.safe == object::id(safe), err::safe_owner_mismatch()); + } + + public fun assert_not_in_set(set: &VecSet, nft: &ID) { + assert!( + !vec_set::contains(set, nft), + 1 + ); + } + + public fun assert_in_set(set: &VecSet, nft: &ID) { + assert!( + vec_set::contains(set, nft), + 1 + ); + } +} diff --git a/sources/utils/err.move b/sources/utils/err.move index a7c65c5c..068dcf2a 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -2,7 +2,7 @@ //! which distinguishes them from errors in other packages. module nft_protocol::err { - const Prefix: u64 = 999100; + const Prefix: u64 = 13370000; public fun nft_not_embedded(): u64 { return Prefix + 01 @@ -103,4 +103,10 @@ module nft_protocol::err { public fun certificate_does_not_correspond_to_nft_given(): u64 { return Prefix + 208 } + + // Safe + + public fun safe_owner_mismatch(): u64 { + return Prefix + 300 + } } From 290a8546fe37622f038f1b53b6f2977c4f113faa Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:15:04 +0200 Subject: [PATCH 02/35] Draft of a heterogenous whitelist --- sources/safe/transfer_whitelist.move | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 sources/safe/transfer_whitelist.move diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move new file mode 100644 index 00000000..821d34cc --- /dev/null +++ b/sources/safe/transfer_whitelist.move @@ -0,0 +1,86 @@ +module nft_protocol::transfer_whitelist { + use std::option::{Self, Option}; + use sui::vec_set::{Self, VecSet}; + use sui::object::{Self, ID, UID}; + + struct Whitelist has key { + id: UID, + /// Which collections does this whitelist apply to? + /// + /// A collection can be inserted only if with `&UID` reference. We treat + /// a reference to `&UID` as an authorization token to avoid generics. + collections: VecSet, + /// If None, then there's no whitelist and everyone is allowed. + /// Otherwise the ID must be in the vec set. + entities: Option>, + } + + /// To add a collection to the list, we need a confirmation by both the + /// whitelist authority (via witness) and the collection creator (via &UID.) + /// + /// If the whitelist authority wants to enable any creator to add their + /// collection to the whitelist, they can reexport this function in their + /// module without the witness protection. However, we opt for witness + /// collection to give the whitelist owner a way to combat spam. + public fun insert_collection( + _witness: W, + collection: &UID, + list: &mut Whitelist, + ) { + vec_set::insert( + &mut list.collections, + object::uid_to_inner(collection) + ); + } + + /// Any collection is allowed to remove itself from any whitelist at any + /// time. + /// + /// It's always the creator's right to decide at any point what entities + /// can transfer NFTs of that collection. + public fun remove_itself(collection: &UID, list: &mut Whitelist) { + vec_set::remove( + &mut list.collections, + object::uid_as_inner(collection) + ); + } + + /// The whitelist owner can remove any collection at any point. + public fun remove_collection( + _witness: W, + collection: &ID, + list: &mut Whitelist, + ) { + vec_set::remove(&mut list.collections, collection); + } + + /// To insert a new entity into a list we need confirmation by the whitelist + /// authority (via witness.) + public fun insert_entity( + _witness: W, + entity: &ID, + list: &mut Whitelist, + ) { + if (option::is_none(&list.entities)) { + list.entities = option::some(vec_set::singleton(entity)); + } else { + vec_set::insert( + option::borrow_mut(&mut list.entities), + *entity, + ); + } + } + + /// The whitelist authority (via witness) can at any point remove any + /// entity from their list. + public fun remove_entity( + _witness: W, + entity: &ID, + list: &mut Whitelist, + ) { + vec_set::remove( + option::borrow_mut(&mut list.entities), + entity, + ); + } +} From b19d277c3f1b589b539e2142806abb73866c1f81 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:15:28 +0200 Subject: [PATCH 03/35] Exposing more logic in Safe --- sources/safe/safe.move | 245 +++++++++++++++++++++++++++++++++++------ sources/utils/err.move | 10 +- 2 files changed, 219 insertions(+), 36 deletions(-) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index 0f67e3e3..fa23c14d 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -1,37 +1,60 @@ module nft_protocol::safe { use sui::object::{ID, UID}; - use sui::vec_set::{Self, VecSet}; + use sui::vec_map::{Self, VecMap}; use sui::tx_context::{Self, TxContext}; use sui::object; use sui::transfer::{share_object, transfer}; use nft_protocol::err; + use nft_protocol::nft::Nft; + use nft_protocol::transfer_whitelist::Whitelist; struct Safe has key { id: UID, - nfts: VecSet, - listed_nfts: VecSet, - exclusively_listed_nfts: VecSet, + nfts: VecMap, } + /// Keeps info about an NFT which enables us to issue transfer caps etc. + struct NftRef has store, copy, drop { + /// Is generated anew every time a counter is incremented from zero to + /// one. + /// + /// We don't use monotonically increasing integer so that we can remove + /// withdrawn NFTs from the map. + version: ID, + /// How many transfer caps are there for this version of Rc. + transfer_cap_counter: u64, + /// Only one `TransferCap` of the latest version can exist. + /// An exlusively listed NFT cannot have its `TransferCap` revoked. + is_exlusively_listed: bool, + } + + /// Whoever owns this object can perform some admin actions against the + /// `Safe` shared object with the corresponding id. struct OwnerCap has key, store { id: UID, safe: ID, } + /// Enables the owner to deposit NFTs into the `Safe`. struct DepositCap has key, store { id: UID, safe: ID, } - struct WithdrawCap has key, store { + /// Enables the owner to transfer given NFT out of the `Safe`. + struct TransferCap has key, store { id: UID, - safe_id: ID, - nft_id: ID, - /// If set to true, only one `WithdrawCap` can be issued for this NFT. + safe: ID, + nft: ID, + version: ID, + /// If set to true, only one `TransferCap` can be issued for this NFT. /// It also cannot be revoked without burning this object. /// /// This is useful for trading flows that cannot guarantee that the NFT /// is claimed atomically. + /// + /// If an NFT is listed exclusively, it cannot be revoked without + /// burning the `TransferCap` first. is_exlusive: bool, } @@ -47,42 +70,161 @@ module nft_protocol::safe { create_safe_(ctx) } - public fun create_withdraw_cap( + /// Creates a `TransferCap` which must be claimed atomically. + /// + /// Otherwise, there's a risk of a race condition as multiple non-exclusive + /// transfer caps can be created. + public fun create_transfer_cap( nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, ctx: &mut TxContext, - ): WithdrawCap { - assert_owner(owner_cap, safe); - assert_in_set(&safe.nfts, &nft); - assert_not_in_set(&safe.exclusively_listed_nfts, &nft); + ): TransferCap { + assert_owner_cap(owner_cap, safe); + assert_contains_nft(&nft, safe); + + let rc = vec_map::get_mut(&mut safe.nfts, &nft); + assert_not_exlusively_listed(rc); + rc.transfer_cap_counter = rc.transfer_cap_counter + 1; + if (rc.transfer_cap_counter == 1) { + rc.version = new_id(ctx); + }; - WithdrawCap { + TransferCap { id: object::new(ctx), - safe_id: object::id(safe), - nft_id: nft, is_exlusive: false, + nft: nft, + safe: object::id(safe), + version: rc.version, } } - public fun create_exlusive_withdraw_cap( + + /// Creates an irrevocable and exclusive transfer cap. + /// + /// Useful for trading contracts which cannot claim an NFT atomically. + public fun create_exlusive_transfer_cap( nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, ctx: &mut TxContext, - ): WithdrawCap { - let cap = create_withdraw_cap(nft, owner_cap, safe, ctx); + ): TransferCap { + assert_owner_cap(owner_cap, safe); + assert_contains_nft(&nft, safe); - assert_not_in_set(&safe.listed_nfts, &nft); - cap.is_exlusive = true; - cap + let rc = vec_map::get_mut(&mut safe.nfts, &nft); + assert_not_exlusively_listed(rc); + + rc.transfer_cap_counter = 1; + rc.version = new_id(ctx); + + TransferCap { + id: object::new(ctx), + is_exlusive: true, + nft: nft, + safe: object::id(safe), + version: rc.version, + } + } + + /// Transfer an NFT from the logical owner to the `Safe`. + public fun deposit_nft( + nft: Nft, + deposit_cap: &DepositCap, + safe: &mut Safe, + ctx: &mut TxContext, + ) { + assert_deposit_cap(deposit_cap, safe); + + vec_map::insert(&mut safe.nfts, object::id(&nft), NftRef { + version: new_id(ctx), + transfer_cap_counter: 0, + is_exlusively_listed: false, + }); + + // TODO: transfer nft as a child obj + abort(0) + } + + /// Remove an NFT from the `Safe` and give it back to the logical owner. + public fun withdraw_nft( + nft: ID, + owner_cap: &OwnerCap, + safe: &mut Safe, + ) { + assert_owner_cap(owner_cap, safe); + assert_contains_nft(&nft, safe); + + // TODO: get NFT + // TODO: move from safe to logical owner + abort(0) + } + + /// Use a transfer cap to get an NFT out of the `Safe`. + /// + /// If the NFT is not exlusively listed, it can happen that the transfer + /// cap is no longer valid. The NFT could've been traded or the trading cap + /// revoked. + public fun transfer_nft( + nft: ID, + transfer_cap: TransferCap, + recipient: address, + whitelist: &Whitelist, + safe: &mut Safe, + ) { + assert_transfer_cap(&transfer_cap, safe); + assert_contains_nft(&nft, safe); + + // TODO: get NFT + // TODO: move from safe to new owner + abort(0) + } + + /// Destroys given transfer cap. This is mainly useful for exlusively listed + /// NFTs. + public fun burn_transfer_cap( + transfer_cap: TransferCap, + safe: &mut Safe, + ) { + assert_transfer_cap(&transfer_cap, safe); + + let TransferCap { + id, + is_exlusive: _, + nft, + safe: _, + version, + } = transfer_cap; + object::delete(id); + + let rc = vec_map::get_mut(&mut safe.nfts, &nft); + if (rc.version == version) { + rc.transfer_cap_counter = rc.transfer_cap_counter - 1; + if (rc.transfer_cap_counter == 0) { + rc.is_exlusively_listed = false; + }; + } + } + + /// Can happen only if the NFT is not listed exlusively. + public fun delist_nft( + nft: &ID, + owner_cap: &OwnerCap, + safe: &mut Safe, + ctx: &mut TxContext, + ) { + assert_owner_cap(owner_cap, safe); + assert_contains_nft(nft, safe); + + let rc = vec_map::get_mut(&mut safe.nfts, nft); + assert_not_exlusively_listed(rc); + + rc.version = new_id(ctx); } fun create_safe_(ctx: &mut TxContext): OwnerCap { let safe = Safe { id: object::new(ctx), - nfts: vec_set::empty(), - listed_nfts: vec_set::empty(), - exclusively_listed_nfts: vec_set::empty(), + nfts: vec_map::empty(), }; let cap = OwnerCap { id: object::new(ctx), @@ -93,25 +235,58 @@ module nft_protocol::safe { cap } + /// Generates a unique ID. + fun new_id(ctx: &mut TxContext): ID { + let new_uid = object::new(ctx); + let new_id = object::uid_to_inner(&new_uid); + object::delete(new_uid); + new_id + } + //------- getters ------- + public fun owner_cap_safe(cap: &OwnerCap): ID { + cap.safe + } + + public fun deposit_cap_safe(cap: &DepositCap): ID { + cap.safe + } + + public fun transfer_cap_safe(cap: &TransferCap): ID { + cap.safe + } + public fun transfer_cap_nft(cap: &TransferCap): ID { + cap.nft + } + public fun transfer_cap_version(cap: &TransferCap): ID { + cap.version + } + public fun transfer_cap_is_exlusive(cap: &TransferCap): bool { + cap.is_exlusive + } + //------- assertions ------- - public fun assert_owner(cap: &OwnerCap, safe: &Safe) { - assert!(cap.safe == object::id(safe), err::safe_owner_mismatch()); + public fun assert_owner_cap(cap: &OwnerCap, safe: &Safe) { + assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); } - public fun assert_not_in_set(set: &VecSet, nft: &ID) { - assert!( - !vec_set::contains(set, nft), - 1 - ); + public fun assert_deposit_cap(cap: &DepositCap, safe: &Safe) { + assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); } - public fun assert_in_set(set: &VecSet, nft: &ID) { + public fun assert_transfer_cap(cap: &TransferCap, safe: &Safe) { + assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); + } + + public fun assert_contains_nft(nft: &ID, safe: &Safe) { assert!( - vec_set::contains(set, nft), - 1 + vec_map::contains(&safe.nfts, nft), err::safe_does_not_contain_nft() ); } + + public fun assert_not_exlusively_listed(rc: &NftRef) { + assert!(!rc.is_exlusively_listed, err::nft_exlusively_listed()); + } } diff --git a/sources/utils/err.move b/sources/utils/err.move index 068dcf2a..65c42e3c 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -106,7 +106,15 @@ module nft_protocol::err { // Safe - public fun safe_owner_mismatch(): u64 { + public fun safe_cap_mismatch(): u64 { return Prefix + 300 } + + public fun safe_does_not_contain_nft(): u64 { + return Prefix + 301 + } + + public fun nft_exlusively_listed(): u64 { + return Prefix + 302 + } } From db39e3d4109603f30a9de452231eee4759d9ef58 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:38:10 +0200 Subject: [PATCH 04/35] Renaming entities to authorities --- sources/safe/transfer_whitelist.move | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index 821d34cc..7a943650 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -12,7 +12,7 @@ module nft_protocol::transfer_whitelist { collections: VecSet, /// If None, then there's no whitelist and everyone is allowed. /// Otherwise the ID must be in the vec set. - entities: Option>, + authorities: Option>, } /// To add a collection to the list, we need a confirmation by both the @@ -36,7 +36,7 @@ module nft_protocol::transfer_whitelist { /// Any collection is allowed to remove itself from any whitelist at any /// time. /// - /// It's always the creator's right to decide at any point what entities + /// It's always the creator's right to decide at any point what authorities /// can transfer NFTs of that collection. public fun remove_itself(collection: &UID, list: &mut Whitelist) { vec_set::remove( @@ -61,11 +61,11 @@ module nft_protocol::transfer_whitelist { entity: &ID, list: &mut Whitelist, ) { - if (option::is_none(&list.entities)) { - list.entities = option::some(vec_set::singleton(entity)); + if (option::is_none(&list.authorities)) { + list.authorities = option::some(vec_set::singleton(entity)); } else { vec_set::insert( - option::borrow_mut(&mut list.entities), + option::borrow_mut(&mut list.authorities), *entity, ); } @@ -79,8 +79,23 @@ module nft_protocol::transfer_whitelist { list: &mut Whitelist, ) { vec_set::remove( - option::borrow_mut(&mut list.entities), + option::borrow_mut(&mut list.authorities), entity, ); } + + public fun can_be_transferred( + collection: &ID, + authority: &ID, + whitelist: &Whitelist, + ): bool { + if (option::is_none(&whitelist.authorities)) { + return true + }; + + let e = option::borrow(&whitelist.authorities); + + vec_set::contains(e, authority) && + vec_set::contains(&whitelist.collections, collection) + } } From cf68477b29fe44fbada0d5749e3c88b213b580c9 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:38:23 +0200 Subject: [PATCH 05/35] Adding whitelisting errs --- sources/utils/err.move | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sources/utils/err.move b/sources/utils/err.move index 65c42e3c..b8a572a4 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -36,7 +36,11 @@ module nft_protocol::err { return Prefix + 08 } - // Supply + public fun not_nft_owner(): u64 { + return Prefix + 09 + } + + // === Supply === public fun supply_policy_mismatch(): u64 { return Prefix + 100 @@ -104,7 +108,7 @@ module nft_protocol::err { return Prefix + 208 } - // Safe + // === Safe === public fun safe_cap_mismatch(): u64 { return Prefix + 300 @@ -117,4 +121,10 @@ module nft_protocol::err { public fun nft_exlusively_listed(): u64 { return Prefix + 302 } + + // === Whitelist === + + public fun authority_not_whitelisted(): u64 { + return Prefix + 400 + } } From c5cb53ca2c08c240ae206b2d0f81c2f87d99a4e3 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:38:48 +0200 Subject: [PATCH 06/35] Consistent code styles --- sources/safe/safe.move | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index fa23c14d..ff2aa5c5 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -243,7 +243,7 @@ module nft_protocol::safe { new_id } - //------- getters ------- + // === Getters === public fun owner_cap_safe(cap: &OwnerCap): ID { cap.safe @@ -266,7 +266,7 @@ module nft_protocol::safe { cap.is_exlusive } - //------- assertions ------- + // === Assertions === public fun assert_owner_cap(cap: &OwnerCap, safe: &Safe) { assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); From 278fcf756c405b924b363c230140374931cd9741 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:39:03 +0200 Subject: [PATCH 07/35] Draft of using logical owner and collection in conjunction with whitelisting transfers --- sources/nft/nft.move | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index 2904b203..c6b9ec4d 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -26,13 +26,17 @@ module nft_protocol::nft { use sui::event; use sui::object::{Self, UID, ID}; - use sui::tx_context::{TxContext}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer; use nft_protocol::err; + use nft_protocol::transfer_whitelist::{Self, Whitelist}; // NFT object with an option to hold `D`ata object struct Nft has key, store { id: UID, + logical_owner: address, + collection: ID, data_id: ID, data: Option, } @@ -159,6 +163,59 @@ module nft_protocol::nft { option::is_none(&nft.data) } + // === Transfer Functions === + + public fun transfer( + nft: Nft, + target: address, + authority: &UID, + whitelist: &Whitelist, + ) { + let is_ok = transfer_whitelist::can_be_transferred( + &nft.collection, + object::uid_as_inner(authority), + whitelist, + ); + assert!(is_ok, err::authority_not_whitelisted()); + + transfer::transfer(nft, target); + } + + public fun transfer_to_owner(nft: Nft) { + transfer::transfer(nft, nft.logical_owner); + } + + public fun transfer_to_object( + nft: Nft, + target: &mut Target, + authority: &UID, + whitelist: &Whitelist, + ) { + let is_ok = transfer_whitelist::can_be_transferred( + &nft.collection, + object::uid_as_inner(authority), + whitelist, + ); + assert!(is_ok, err::authority_not_whitelisted()); + + // TODO: move to given target + abort(0); + } + + public fun priviledged_transfer_to_object( + nft: Nft, + target: &mut Target, + ctx: &mut TxContext, + ) { + assert!( + nft.logical_owner == tx_context::sender(ctx), + err::not_nft_owner(), + ); + + // TODO: move to given target + abort(0); + } + // === Getter Functions === public fun id( From f13154b057a8d3408f814168c237b705340dc11f Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:46:34 +0200 Subject: [PATCH 08/35] Fixing a bug where for delisting we only rotate version but not counter --- sources/safe/safe.move | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index ff2aa5c5..e3e4622b 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -219,6 +219,7 @@ module nft_protocol::safe { assert_not_exlusively_listed(rc); rc.version = new_id(ctx); + rc.transfer_cap_counter = 0; } fun create_safe_(ctx: &mut TxContext): OwnerCap { From 321672e97e1ba68039db916df3f6df0fd559994c Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 26 Oct 2022 19:59:48 +0200 Subject: [PATCH 09/35] Removing the store ability --- sources/nft/nft.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index c6b9ec4d..61ae50e3 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -33,7 +33,7 @@ module nft_protocol::nft { use nft_protocol::transfer_whitelist::{Self, Whitelist}; // NFT object with an option to hold `D`ata object - struct Nft has key, store { + struct Nft has key { id: UID, logical_owner: address, collection: ID, From bf1339524acb680f2cc47a7ee942f272820bb075 Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:06:23 +0200 Subject: [PATCH 10/35] Adapted code to new NFT fields --- sources/nft/nft.move | 15 ++++++++++++++- sources/nft/nfts/c_nft.move | 12 +++++++++--- sources/nft/nfts/collectible.move | 2 ++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index b8b8e500..a221165c 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -54,6 +54,8 @@ module nft_protocol::nft { /// Create a loose `Nft` and returns it. public fun mint_nft_loose( data_id: ID, + logical_owner: address, + collection: ID, ctx: &mut TxContext, ): Nft { let nft_id = object::new(ctx); @@ -67,6 +69,8 @@ module nft_protocol::nft { Nft { id: nft_id, + logical_owner, + collection, data_id: data_id, data: option::none(), } @@ -75,6 +79,8 @@ module nft_protocol::nft { /// Create a embeded `Nft` and returns it. public fun mint_nft_embedded( data_id: ID, + logical_owner: address, + collection: ID, data: D, ctx: &mut TxContext, ): Nft { @@ -89,6 +95,8 @@ module nft_protocol::nft { Nft { id: nft_id, + logical_owner, + collection, data_id: data_id, data: option::some(data), } @@ -125,6 +133,8 @@ module nft_protocol::nft { let Nft { id, + logical_owner: _, + collection: _, data_id: _, data, } = nft; @@ -148,6 +158,8 @@ module nft_protocol::nft { let Nft { id, + logical_owner: _, + collection: _, data_id: _, data, } = nft; @@ -182,7 +194,8 @@ module nft_protocol::nft { } public fun transfer_to_owner(nft: Nft) { - transfer::transfer(nft, nft.logical_owner); + let logical_owner = nft.logical_owner; + transfer::transfer(nft, logical_owner); } public fun transfer_to_object( diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index e697f1d6..f922390a 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -300,6 +300,8 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose( nft_data_id(nft_data), + recipient, + nft_data.collection_id, ctx, ); @@ -360,6 +362,8 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose( object::uid_to_inner(&combo_data.id), + recipient, + combo_data.collection_id, ctx, ); @@ -389,15 +393,15 @@ module nft_protocol::c_nft { /// objects when we merge them, therefore we maintain consistency. public entry fun split_c_nft( nft: Nft>, - c_nft_data: &mut Composable, + combo_data: &mut Composable, nfts_data: vector>, ctx: &mut TxContext, ) { // Assert that nft pointer corresponds to c_nft_data // If so, then burn pointer and mint pointer for each nfts_data - assert!(nft::data_id(&nft) == id(c_nft_data), err::nft_data_mismatch()); + assert!(nft::data_id(&nft) == id(combo_data), err::nft_data_mismatch()); - supply::decrement_supply(&mut c_nft_data.supply, 1); + supply::decrement_supply(&mut combo_data.supply, 1); nft::burn_loose_nft(nft); let len = vector::length(&nfts_data); @@ -407,6 +411,8 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose>( id(&data), + tx_context::sender(ctx), + combo_data.collection_id, ctx, ); diff --git a/sources/nft/nfts/collectible.move b/sources/nft/nfts/collectible.move index 597d8999..b5102e36 100644 --- a/sources/nft/nfts/collectible.move +++ b/sources/nft/nfts/collectible.move @@ -164,6 +164,8 @@ module nft_protocol::collectible { let nft = nft::mint_nft_loose( nft_data_id(nft_data), + recipient, + nft_data.collection_id, ctx, ); From 050912b2f0a71c56a21d0c487d13cba6f33f2966 Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:06:51 +0200 Subject: [PATCH 11/35] New Embedded NFTs Flow --- sources/examples/suimarines.move | 6 +-- sources/launchpad/market/fixed_price.move | 16 +++++++- sources/launchpad/sale.move | 12 +++++- sources/launchpad/slingshot.move | 36 ++++++++++++++---- sources/nft/nfts/unique_nft.move | 46 ++++++----------------- 5 files changed, 68 insertions(+), 48 deletions(-) diff --git a/sources/examples/suimarines.move b/sources/examples/suimarines.move index e7b32044..959cf253 100644 --- a/sources/examples/suimarines.move +++ b/sources/examples/suimarines.move @@ -43,7 +43,7 @@ module nft_protocol::suimarines { ); } - public entry fun mint_nft( + public entry fun mint_nft_data( name: vector, description: vector, url: vector, @@ -54,15 +54,13 @@ module nft_protocol::suimarines { launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - unique_nft::mint_regulated_nft( + unique_nft::mint_regulated_nft_data( name, description, url, attribute_keys, attribute_values, mint_authority, - sale_index, - launchpad, ctx, ); } diff --git a/sources/launchpad/market/fixed_price.move b/sources/launchpad/market/fixed_price.move index 7192d371..5a67aab7 100644 --- a/sources/launchpad/market/fixed_price.move +++ b/sources/launchpad/market/fixed_price.move @@ -159,6 +159,7 @@ module nft_protocol::fixed_price { let launchpad_id = slingshot::id(slingshot); let receiver = slingshot::receiver(slingshot); + let collection_id = slingshot::collection_id(slingshot); let sale = slingshot::sale_mut(slingshot, tier_index); // Infer that sales is NOT whitelisted @@ -179,7 +180,12 @@ module nft_protocol::fixed_price { ctx ); - let certificate = sale::issue_nft_certificate(sale, launchpad_id, ctx); + let certificate = sale::issue_nft_certificate( + sale, + launchpad_id, + collection_id, + ctx + ); transfer::transfer( certificate, @@ -206,6 +212,7 @@ module nft_protocol::fixed_price { let launchpad_id = slingshot::id(slingshot); let receiver = slingshot::receiver(slingshot); + let collection_id = slingshot::collection_id(slingshot); let sale = slingshot::sale_mut(slingshot, tier_index); // Infer that sales is whitelisted @@ -234,7 +241,12 @@ module nft_protocol::fixed_price { whitelist::burn_whitelist_token(whitelist_token); - let certificate = sale::issue_nft_certificate(sale, launchpad_id, ctx); + let certificate = sale::issue_nft_certificate( + sale, + launchpad_id, + collection_id, + ctx + ); transfer::transfer( certificate, diff --git a/sources/launchpad/sale.move b/sources/launchpad/sale.move index c486c9f3..1854f0e9 100644 --- a/sources/launchpad/sale.move +++ b/sources/launchpad/sale.move @@ -36,6 +36,7 @@ module nft_protocol::sale { struct NftCertificate has key, store { id: UID, launchpad_id: ID, + collection_id: ID, nft_id: ID, } @@ -91,13 +92,15 @@ module nft_protocol::sale { public fun issue_nft_certificate( sale: &mut Sale, launchpad_id: ID, + collection_id: ID, ctx: &mut TxContext, ): NftCertificate { let nft_id = pop_nft(sale); let certificate = NftCertificate { id: object::new(ctx), - launchpad_id: launchpad_id, + launchpad_id, + collection_id, nft_id: nft_id, }; @@ -110,6 +113,7 @@ module nft_protocol::sale { let NftCertificate { id, launchpad_id: _, + collection_id: _, nft_id: _, } = certificate; @@ -176,4 +180,10 @@ module nft_protocol::sale { ): bool { sale.whitelisted } + + public fun collection_id( + certificate: &NftCertificate, + ): ID { + certificate.collection_id + } } diff --git a/sources/launchpad/slingshot.move b/sources/launchpad/slingshot.move index 24b3832f..b00b4667 100644 --- a/sources/launchpad/slingshot.move +++ b/sources/launchpad/slingshot.move @@ -13,7 +13,7 @@ module nft_protocol::slingshot { use sui::object::{Self, ID , UID}; use sui::tx_context::{Self, TxContext}; - use nft_protocol::nft::{Self, Nft}; + use nft_protocol::nft; use nft_protocol::err; use nft_protocol::sale::{Self, Sale, NftCertificate}; @@ -126,20 +126,33 @@ module nft_protocol::slingshot { /// of the NFT. Since the slingshot is a shared object anyone can mention it /// in the function signature and therefore be able to mention its child /// objects as well, the NFTs owned by it. - public entry fun claim_nft_embedded( + public entry fun claim_nft_embedded( slingshot: &Slingshot, - nft: Nft, + // TODO: nft_data is a shared object, we should confirm that we can + // add it directly as a parameter since this transfers ownership to the + // function's scope + nft_data: D, certificate: NftCertificate, recipient: address, + ctx: &mut TxContext, ) { assert!( - nft::id(&nft) == sale::nft_id(&certificate), + object::id(&nft_data) == sale::nft_id(&certificate), err::certificate_does_not_correspond_to_nft_given() ); + assert!(is_embedded(slingshot), err::nft_not_embedded()); + + let nft = nft::mint_nft_loose( + object::id(&nft_data), + recipient, + sale::collection_id(&certificate), + ctx, + ); + sale::burn_certificate(certificate); - assert!(is_embedded(slingshot), err::nft_not_embedded()); + nft::join_nft_data(&mut nft, nft_data); transfer::transfer( nft, @@ -168,8 +181,6 @@ module nft_protocol::slingshot { err::certificate_does_not_correspond_to_nft_given() ); - sale::burn_certificate(certificate); - assert!(!is_embedded(slingshot), err::nft_not_loose()); // We are currently not increasing the current supply of the NFT @@ -177,9 +188,13 @@ module nft_protocol::slingshot { // of supply). let nft = nft::mint_nft_loose( object::id(nft_data), + recipient, + sale::collection_id(&certificate), ctx, ); + sale::burn_certificate(certificate); + transfer::transfer( nft, recipient, @@ -228,6 +243,13 @@ module nft_protocol::slingshot { object::uid_as_inner(&slingshot.id) } + /// Get the Slingshot's `collection_id` + public fun collection_id( + slingshot: &Slingshot, + ): ID { + slingshot.collection_id + } + /// Get the Slingshot's `live` public fun live( slingshot: &Slingshot, diff --git a/sources/nft/nfts/unique_nft.move b/sources/nft/nfts/unique_nft.move index 2c658e8c..769a2637 100644 --- a/sources/nft/nfts/unique_nft.move +++ b/sources/nft/nfts/unique_nft.move @@ -17,8 +17,6 @@ module nft_protocol::unique_nft { use nft_protocol::collection::{Self, MintAuthority}; use nft_protocol::utils::{to_string_vector}; use nft_protocol::supply_policy; - use nft_protocol::slingshot::{Self, Slingshot}; - use nft_protocol::sale; use nft_protocol::nft::{Self, Nft}; /// An NFT `Unique` data object with standard fields. @@ -55,6 +53,7 @@ module nft_protocol::unique_nft { // === Functions exposed to Witness Module === + // TODO: update comment /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. /// Invokes `mint_to_launchpad()`. /// Mints an NFT from a `Collection` with unregulated supply. @@ -63,17 +62,13 @@ module nft_protocol::unique_nft { /// if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - public fun mint_unregulated_nft( + public fun mint_unregulated_nft_data( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint: &MintAuthority, - sale_index: u64, - // TODO: Ideally we do not take a mutable reference such that - // no lock is needed - launchpad: &mut Slingshot, ctx: &mut TxContext, ) { // Assert that it has an unregulated supply policy @@ -90,15 +85,14 @@ module nft_protocol::unique_nft { to_string_vector(&mut attribute_values), ); - mint_to_launchpad( + mint_and_share_data( args, collection::mint_collection_id(mint), - sale_index, - launchpad, ctx, ); } + // TODO: updated comment /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. /// Invokes `mint_to_launchpad()`. /// Mints an NFT from a `Collection` with regulated supply. @@ -107,15 +101,13 @@ module nft_protocol::unique_nft { /// if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - public fun mint_regulated_nft( + public fun mint_regulated_nft_data( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint: &mut MintAuthority, - sale_index: u64, - launchpad: &mut Slingshot, ctx: &mut TxContext, ) { // Assert that it has regulated supply policy @@ -134,11 +126,9 @@ module nft_protocol::unique_nft { collection::increment_supply(mint, 1); - mint_to_launchpad( + mint_and_share_data( args, collection::mint_collection_id(mint), - sale_index, - launchpad, ctx, ); } @@ -316,6 +306,8 @@ module nft_protocol::unique_nft { let nft = nft::mint_nft_embedded( nft_data_id(&nft_data), + recipient, + collection_id, nft_data, ctx ); @@ -326,11 +318,10 @@ module nft_protocol::unique_nft { ); } - fun mint_to_launchpad( + // TODO: add documentation + fun mint_and_share_data( args: MintArgs, collection_id: ID, - sale_index: u64, - launchpad: &mut Slingshot, ctx: &mut TxContext, ) { let data_id = object::new(ctx); @@ -342,7 +333,7 @@ module nft_protocol::unique_nft { } ); - let nft_data = Unique { + let data = Unique { id: data_id, name: args.name, description: args.description, @@ -351,20 +342,7 @@ module nft_protocol::unique_nft { attributes: args.attributes, }; - let nft = nft::mint_nft_embedded( - nft_data_id(&nft_data), - nft_data, - ctx - ); - - let sale = slingshot::sale_mut(launchpad, sale_index); - - sale::add_nft(sale, nft::id(&nft)); - - transfer::transfer_to_object( - nft, - launchpad, - ); + transfer::share_object(data); } fun burn_nft_( From 84745273a8783df0cdb8637a0d0f644991d208f7 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 31 Oct 2022 12:56:33 +0100 Subject: [PATCH 12/35] Implementing Safe with ObjectBag --- Move.toml | 2 +- sources/nft/nft.move | 14 ++--- sources/nft/nfts/social_profile.move | 2 + sources/safe/safe.move | 90 +++++++++++++++++++--------- sources/safe/transfer_whitelist.move | 2 +- sources/utils/err.move | 8 +++ 6 files changed, 81 insertions(+), 37 deletions(-) diff --git a/Move.toml b/Move.toml index 443d9439..7bdd2971 100644 --- a/Move.toml +++ b/Move.toml @@ -3,7 +3,7 @@ name = "NftProtocol" version = "0.6.0" [dependencies] -Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "481dba3" } +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } [addresses] nft_protocol = "0x0" diff --git a/sources/nft/nft.move b/sources/nft/nft.move index a221165c..43fb7453 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -33,7 +33,7 @@ module nft_protocol::nft { use nft_protocol::transfer_whitelist::{Self, Whitelist}; // NFT object with an option to hold `D`ata object - struct Nft has key { + struct Nft has key, store { id: UID, logical_owner: address, collection: ID, @@ -200,7 +200,7 @@ module nft_protocol::nft { public fun transfer_to_object( nft: Nft, - target: &mut Target, + _target: &mut Target, authority: &UID, whitelist: &Whitelist, ) { @@ -211,13 +211,13 @@ module nft_protocol::nft { ); assert!(is_ok, err::authority_not_whitelisted()); - // TODO: move to given target - abort(0); + // TODO: https://github.com/MystenLabs/sui/issues/5584 + abort(0) } public fun priviledged_transfer_to_object( nft: Nft, - target: &mut Target, + _target: &mut Target, ctx: &mut TxContext, ) { assert!( @@ -225,8 +225,8 @@ module nft_protocol::nft { err::not_nft_owner(), ); - // TODO: move to given target - abort(0); + // TODO: https://github.com/MystenLabs/sui/issues/5584 + abort(0) } // === Getter Functions === diff --git a/sources/nft/nfts/social_profile.move b/sources/nft/nfts/social_profile.move index d51f90f3..ee10348c 100644 --- a/sources/nft/nfts/social_profile.move +++ b/sources/nft/nfts/social_profile.move @@ -171,6 +171,8 @@ module nft_protocol::social_profile { let nft = nft::mint_nft_embedded( nft_data_id(&nft_data), + recipient, + nft_data_id(&nft_data), // TODO: what is the collection id here? nft_data, ctx ); diff --git a/sources/safe/safe.move b/sources/safe/safe.move index e3e4622b..261ad95b 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -5,12 +5,14 @@ module nft_protocol::safe { use sui::object; use sui::transfer::{share_object, transfer}; use nft_protocol::err; - use nft_protocol::nft::Nft; + use nft_protocol::nft::{Self, Nft}; use nft_protocol::transfer_whitelist::Whitelist; + use sui::object_bag::{Self, ObjectBag}; struct Safe has key { id: UID, - nfts: VecMap, + refs: VecMap, + nfts: ObjectBag, } /// Keeps info about an NFT which enables us to issue transfer caps etc. @@ -83,7 +85,8 @@ module nft_protocol::safe { assert_owner_cap(owner_cap, safe); assert_contains_nft(&nft, safe); - let rc = vec_map::get_mut(&mut safe.nfts, &nft); + let safe_id = object::id(safe); + let rc = vec_map::get_mut(&mut safe.refs, &nft); assert_not_exlusively_listed(rc); rc.transfer_cap_counter = rc.transfer_cap_counter + 1; if (rc.transfer_cap_counter == 1) { @@ -94,7 +97,7 @@ module nft_protocol::safe { id: object::new(ctx), is_exlusive: false, nft: nft, - safe: object::id(safe), + safe: safe_id, version: rc.version, } } @@ -111,7 +114,8 @@ module nft_protocol::safe { assert_owner_cap(owner_cap, safe); assert_contains_nft(&nft, safe); - let rc = vec_map::get_mut(&mut safe.nfts, &nft); + let safe_id = object::id(safe); + let rc = vec_map::get_mut(&mut safe.refs, &nft); assert_not_exlusively_listed(rc); rc.transfer_cap_counter = 1; @@ -121,7 +125,7 @@ module nft_protocol::safe { id: object::new(ctx), is_exlusive: true, nft: nft, - safe: object::id(safe), + safe: safe_id, version: rc.version, } } @@ -135,28 +139,33 @@ module nft_protocol::safe { ) { assert_deposit_cap(deposit_cap, safe); - vec_map::insert(&mut safe.nfts, object::id(&nft), NftRef { + let nft_id = object::id(&nft); + + vec_map::insert(&mut safe.refs, nft_id, NftRef { version: new_id(ctx), transfer_cap_counter: 0, is_exlusively_listed: false, }); - // TODO: transfer nft as a child obj - abort(0) + object_bag::add(&mut safe.nfts, nft_id, nft); } /// Remove an NFT from the `Safe` and give it back to the logical owner. - public fun withdraw_nft( - nft: ID, + public fun withdraw_nft( + nft_id: ID, owner_cap: &OwnerCap, safe: &mut Safe, ) { assert_owner_cap(owner_cap, safe); - assert_contains_nft(&nft, safe); + assert_contains_nft(&nft_id, safe); - // TODO: get NFT - // TODO: move from safe to logical owner - abort(0) + let (_, ref) = vec_map::remove(&mut safe.refs, &nft_id); + // an exlusively listed NFT must first have its transfer cap burned + // via fun burn_transfer_cap + assert_not_exlusively_listed(&ref); + + let nft = object_bag::remove>(&mut safe.nfts, nft_id); + nft::transfer_to_owner(nft); } /// Use a transfer cap to get an NFT out of the `Safe`. @@ -164,19 +173,32 @@ module nft_protocol::safe { /// If the NFT is not exlusively listed, it can happen that the transfer /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. - public fun transfer_nft( - nft: ID, + public fun transfer_nft( + nft_id: ID, transfer_cap: TransferCap, recipient: address, + authority: &UID, whitelist: &Whitelist, safe: &mut Safe, ) { - assert_transfer_cap(&transfer_cap, safe); - assert_contains_nft(&nft, safe); + assert_transfer_cap_of_safe(&transfer_cap, safe); + assert_nft_of_transfer_cap(&nft_id, &transfer_cap); + assert_contains_nft(&nft_id, safe); + + let (_, ref) = vec_map::remove(&mut safe.refs, &nft_id); + assert_version_match(&ref, &transfer_cap); - // TODO: get NFT - // TODO: move from safe to new owner - abort(0) + let TransferCap { + id, + safe: _, + nft: _, + version: _, + is_exlusive: _, + } = transfer_cap; + object::delete(id); + + let nft = object_bag::remove>(&mut safe.nfts, nft_id); + nft::transfer(nft, recipient, authority, whitelist); } /// Destroys given transfer cap. This is mainly useful for exlusively listed @@ -185,7 +207,7 @@ module nft_protocol::safe { transfer_cap: TransferCap, safe: &mut Safe, ) { - assert_transfer_cap(&transfer_cap, safe); + assert_transfer_cap_of_safe(&transfer_cap, safe); let TransferCap { id, @@ -196,7 +218,7 @@ module nft_protocol::safe { } = transfer_cap; object::delete(id); - let rc = vec_map::get_mut(&mut safe.nfts, &nft); + let rc = vec_map::get_mut(&mut safe.refs, &nft); if (rc.version == version) { rc.transfer_cap_counter = rc.transfer_cap_counter - 1; if (rc.transfer_cap_counter == 0) { @@ -205,6 +227,9 @@ module nft_protocol::safe { } } + /// Changes the transfer ref version, thereby invalidating all existing + /// `TransferCap` objects. + /// /// Can happen only if the NFT is not listed exlusively. public fun delist_nft( nft: &ID, @@ -215,7 +240,7 @@ module nft_protocol::safe { assert_owner_cap(owner_cap, safe); assert_contains_nft(nft, safe); - let rc = vec_map::get_mut(&mut safe.nfts, nft); + let rc = vec_map::get_mut(&mut safe.refs, nft); assert_not_exlusively_listed(rc); rc.version = new_id(ctx); @@ -225,7 +250,8 @@ module nft_protocol::safe { fun create_safe_(ctx: &mut TxContext): OwnerCap { let safe = Safe { id: object::new(ctx), - nfts: vec_map::empty(), + refs: vec_map::empty(), + nfts: object_bag::new(ctx), }; let cap = OwnerCap { id: object::new(ctx), @@ -277,17 +303,25 @@ module nft_protocol::safe { assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); } - public fun assert_transfer_cap(cap: &TransferCap, safe: &Safe) { + public fun assert_transfer_cap_of_safe(cap: &TransferCap, safe: &Safe) { assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); } + public fun assert_nft_of_transfer_cap(nft: &ID, cap: &TransferCap) { + assert!(&cap.nft == nft, err::transfer_cap_nft_mismatch()); + } + public fun assert_contains_nft(nft: &ID, safe: &Safe) { assert!( - vec_map::contains(&safe.nfts, nft), err::safe_does_not_contain_nft() + vec_map::contains(&safe.refs, nft), err::safe_does_not_contain_nft() ); } public fun assert_not_exlusively_listed(rc: &NftRef) { assert!(!rc.is_exlusively_listed, err::nft_exlusively_listed()); } + + public fun assert_version_match(rc: &NftRef, cap: &TransferCap) { + assert!(rc.version == cap.version, err::transfer_cap_expired()); + } } diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index 7a943650..72d910bf 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -62,7 +62,7 @@ module nft_protocol::transfer_whitelist { list: &mut Whitelist, ) { if (option::is_none(&list.authorities)) { - list.authorities = option::some(vec_set::singleton(entity)); + list.authorities = option::some(vec_set::singleton(*entity)); } else { vec_set::insert( option::borrow_mut(&mut list.authorities), diff --git a/sources/utils/err.move b/sources/utils/err.move index b8a572a4..f0f7b757 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -122,6 +122,14 @@ module nft_protocol::err { return Prefix + 302 } + public fun transfer_cap_nft_mismatch(): u64 { + return Prefix + 303 + } + + public fun transfer_cap_expired(): u64 { + return Prefix + 304 + } + // === Whitelist === public fun authority_not_whitelisted(): u64 { From a20f3301cf75e108dc91b0d5b44bd6b2cfca03c0 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 31 Oct 2022 12:59:45 +0100 Subject: [PATCH 13/35] Renaming rc to ref --- sources/safe/safe.move | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index 261ad95b..b80f5c90 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -23,7 +23,7 @@ module nft_protocol::safe { /// We don't use monotonically increasing integer so that we can remove /// withdrawn NFTs from the map. version: ID, - /// How many transfer caps are there for this version of Rc. + /// How many transfer caps are there for this version. transfer_cap_counter: u64, /// Only one `TransferCap` of the latest version can exist. /// An exlusively listed NFT cannot have its `TransferCap` revoked. @@ -86,11 +86,11 @@ module nft_protocol::safe { assert_contains_nft(&nft, safe); let safe_id = object::id(safe); - let rc = vec_map::get_mut(&mut safe.refs, &nft); - assert_not_exlusively_listed(rc); - rc.transfer_cap_counter = rc.transfer_cap_counter + 1; - if (rc.transfer_cap_counter == 1) { - rc.version = new_id(ctx); + let ref = vec_map::get_mut(&mut safe.refs, &nft); + assert_not_exlusively_listed(ref); + ref.transfer_cap_counter = ref.transfer_cap_counter + 1; + if (ref.transfer_cap_counter == 1) { + ref.version = new_id(ctx); }; TransferCap { @@ -98,7 +98,7 @@ module nft_protocol::safe { is_exlusive: false, nft: nft, safe: safe_id, - version: rc.version, + version: ref.version, } } @@ -115,18 +115,18 @@ module nft_protocol::safe { assert_contains_nft(&nft, safe); let safe_id = object::id(safe); - let rc = vec_map::get_mut(&mut safe.refs, &nft); - assert_not_exlusively_listed(rc); + let ref = vec_map::get_mut(&mut safe.refs, &nft); + assert_not_exlusively_listed(ref); - rc.transfer_cap_counter = 1; - rc.version = new_id(ctx); + ref.transfer_cap_counter = 1; + ref.version = new_id(ctx); TransferCap { id: object::new(ctx), is_exlusive: true, nft: nft, safe: safe_id, - version: rc.version, + version: ref.version, } } @@ -218,11 +218,11 @@ module nft_protocol::safe { } = transfer_cap; object::delete(id); - let rc = vec_map::get_mut(&mut safe.refs, &nft); - if (rc.version == version) { - rc.transfer_cap_counter = rc.transfer_cap_counter - 1; - if (rc.transfer_cap_counter == 0) { - rc.is_exlusively_listed = false; + let ref = vec_map::get_mut(&mut safe.refs, &nft); + if (ref.version == version) { + ref.transfer_cap_counter = ref.transfer_cap_counter - 1; + if (ref.transfer_cap_counter == 0) { + ref.is_exlusively_listed = false; }; } } @@ -240,11 +240,11 @@ module nft_protocol::safe { assert_owner_cap(owner_cap, safe); assert_contains_nft(nft, safe); - let rc = vec_map::get_mut(&mut safe.refs, nft); - assert_not_exlusively_listed(rc); + let ref = vec_map::get_mut(&mut safe.refs, nft); + assert_not_exlusively_listed(ref); - rc.version = new_id(ctx); - rc.transfer_cap_counter = 0; + ref.version = new_id(ctx); + ref.transfer_cap_counter = 0; } fun create_safe_(ctx: &mut TxContext): OwnerCap { @@ -317,11 +317,11 @@ module nft_protocol::safe { ); } - public fun assert_not_exlusively_listed(rc: &NftRef) { - assert!(!rc.is_exlusively_listed, err::nft_exlusively_listed()); + public fun assert_not_exlusively_listed(ref: &NftRef) { + assert!(!ref.is_exlusively_listed, err::nft_exlusively_listed()); } - public fun assert_version_match(rc: &NftRef, cap: &TransferCap) { - assert!(rc.version == cap.version, err::transfer_cap_expired()); + public fun assert_version_match(ref: &NftRef, cap: &TransferCap) { + assert!(ref.version == cap.version, err::transfer_cap_expired()); } } From 7da71331c0e5cac8f5203e82edbb36ac08b13174 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 31 Oct 2022 14:03:10 +0100 Subject: [PATCH 14/35] Using witness for whitelisting which enables us to get rid of colletion_id on the Nft wrapper type. The witness is stored on the whitelist type as string to avoid generics. --- sources/launchpad/slingshot.move | 2 - sources/nft/nft.move | 51 ++-------------- sources/nft/nfts/c_nft.move | 3 - sources/nft/nfts/collectible.move | 1 - sources/nft/nfts/social_profile.move | 1 - sources/nft/nfts/unique_nft.move | 1 - sources/safe/safe.move | 6 +- sources/safe/transfer_whitelist.move | 91 +++++++++++++++------------- 8 files changed, 57 insertions(+), 99 deletions(-) diff --git a/sources/launchpad/slingshot.move b/sources/launchpad/slingshot.move index b00b4667..822ab48f 100644 --- a/sources/launchpad/slingshot.move +++ b/sources/launchpad/slingshot.move @@ -146,7 +146,6 @@ module nft_protocol::slingshot { let nft = nft::mint_nft_loose( object::id(&nft_data), recipient, - sale::collection_id(&certificate), ctx, ); @@ -189,7 +188,6 @@ module nft_protocol::slingshot { let nft = nft::mint_nft_loose( object::id(nft_data), recipient, - sale::collection_id(&certificate), ctx, ); diff --git a/sources/nft/nft.move b/sources/nft/nft.move index 43fb7453..05038a16 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -26,7 +26,7 @@ module nft_protocol::nft { use sui::event; use sui::object::{Self, UID, ID}; - use sui::tx_context::{Self, TxContext}; + use sui::tx_context::TxContext; use sui::transfer; use nft_protocol::err; @@ -36,7 +36,6 @@ module nft_protocol::nft { struct Nft has key, store { id: UID, logical_owner: address, - collection: ID, data_id: ID, data: Option, } @@ -55,7 +54,6 @@ module nft_protocol::nft { public fun mint_nft_loose( data_id: ID, logical_owner: address, - collection: ID, ctx: &mut TxContext, ): Nft { let nft_id = object::new(ctx); @@ -70,7 +68,6 @@ module nft_protocol::nft { Nft { id: nft_id, logical_owner, - collection, data_id: data_id, data: option::none(), } @@ -80,7 +77,6 @@ module nft_protocol::nft { public fun mint_nft_embedded( data_id: ID, logical_owner: address, - collection: ID, data: D, ctx: &mut TxContext, ): Nft { @@ -96,7 +92,6 @@ module nft_protocol::nft { Nft { id: nft_id, logical_owner, - collection, data_id: data_id, data: option::some(data), } @@ -134,7 +129,6 @@ module nft_protocol::nft { let Nft { id, logical_owner: _, - collection: _, data_id: _, data, } = nft; @@ -159,7 +153,6 @@ module nft_protocol::nft { let Nft { id, logical_owner: _, - collection: _, data_id: _, data, } = nft; @@ -177,15 +170,14 @@ module nft_protocol::nft { // === Transfer Functions === - public fun transfer( + public fun transfer( nft: Nft, target: address, - authority: &UID, - whitelist: &Whitelist, + authority: Auth, + whitelist: &Whitelist, ) { - let is_ok = transfer_whitelist::can_be_transferred( - &nft.collection, - object::uid_as_inner(authority), + let is_ok = transfer_whitelist::can_be_transferred( + authority, whitelist, ); assert!(is_ok, err::authority_not_whitelisted()); @@ -198,37 +190,6 @@ module nft_protocol::nft { transfer::transfer(nft, logical_owner); } - public fun transfer_to_object( - nft: Nft, - _target: &mut Target, - authority: &UID, - whitelist: &Whitelist, - ) { - let is_ok = transfer_whitelist::can_be_transferred( - &nft.collection, - object::uid_as_inner(authority), - whitelist, - ); - assert!(is_ok, err::authority_not_whitelisted()); - - // TODO: https://github.com/MystenLabs/sui/issues/5584 - abort(0) - } - - public fun priviledged_transfer_to_object( - nft: Nft, - _target: &mut Target, - ctx: &mut TxContext, - ) { - assert!( - nft.logical_owner == tx_context::sender(ctx), - err::not_nft_owner(), - ); - - // TODO: https://github.com/MystenLabs/sui/issues/5584 - abort(0) - } - // === Getter Functions === public fun id( diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index c7a7666b..32fbc6a2 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -301,7 +301,6 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose( nft_data_id(nft_data), recipient, - nft_data.collection_id, ctx, ); @@ -363,7 +362,6 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose( object::uid_to_inner(&combo_data.id), recipient, - combo_data.collection_id, ctx, ); @@ -412,7 +410,6 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose>( id(&data), tx_context::sender(ctx), - combo_data.collection_id, ctx, ); diff --git a/sources/nft/nfts/collectible.move b/sources/nft/nfts/collectible.move index 3c7c1683..a3c2ad61 100644 --- a/sources/nft/nfts/collectible.move +++ b/sources/nft/nfts/collectible.move @@ -165,7 +165,6 @@ module nft_protocol::collectible { let nft = nft::mint_nft_loose( nft_data_id(nft_data), recipient, - nft_data.collection_id, ctx, ); diff --git a/sources/nft/nfts/social_profile.move b/sources/nft/nfts/social_profile.move index ee10348c..48eb783e 100644 --- a/sources/nft/nfts/social_profile.move +++ b/sources/nft/nfts/social_profile.move @@ -172,7 +172,6 @@ module nft_protocol::social_profile { let nft = nft::mint_nft_embedded( nft_data_id(&nft_data), recipient, - nft_data_id(&nft_data), // TODO: what is the collection id here? nft_data, ctx ); diff --git a/sources/nft/nfts/unique_nft.move b/sources/nft/nfts/unique_nft.move index 885d900d..08659da9 100644 --- a/sources/nft/nfts/unique_nft.move +++ b/sources/nft/nfts/unique_nft.move @@ -321,7 +321,6 @@ module nft_protocol::unique_nft { let nft = nft::mint_nft_embedded( nft_data_id(&nft_data), recipient, - collection_id, nft_data, ctx ); diff --git a/sources/safe/safe.move b/sources/safe/safe.move index b80f5c90..4f7269cd 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -173,12 +173,12 @@ module nft_protocol::safe { /// If the NFT is not exlusively listed, it can happen that the transfer /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. - public fun transfer_nft( + public fun transfer_nft( nft_id: ID, transfer_cap: TransferCap, recipient: address, - authority: &UID, - whitelist: &Whitelist, + authority: Auth, + whitelist: &Whitelist, safe: &mut Safe, ) { assert_transfer_cap_of_safe(&transfer_cap, safe); diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index 72d910bf..b12d13c8 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -1,18 +1,21 @@ module nft_protocol::transfer_whitelist { use std::option::{Self, Option}; + use std::type_name; use sui::vec_set::{Self, VecSet}; - use sui::object::{Self, ID, UID}; + use sui::object::UID; + use std::ascii::String; struct Whitelist has key { id: UID, /// Which collections does this whitelist apply to? /// - /// A collection can be inserted only if with `&UID` reference. We treat - /// a reference to `&UID` as an authorization token to avoid generics. - collections: VecSet, + /// This is a string gotten from `type_name::get` function + collections: VecSet, /// If None, then there's no whitelist and everyone is allowed. - /// Otherwise the ID must be in the vec set. - authorities: Option>, + /// + /// Otherwise we use a witness pattern but store the witness object as + /// the output of `type_name::get`. + authorities: Option>, } /// To add a collection to the list, we need a confirmation by both the @@ -22,15 +25,12 @@ module nft_protocol::transfer_whitelist { /// collection to the whitelist, they can reexport this function in their /// module without the witness protection. However, we opt for witness /// collection to give the whitelist owner a way to combat spam. - public fun insert_collection( - _witness: W, - collection: &UID, - list: &mut Whitelist, + public fun insert_collection( + _whitelist_witness: WW, + _collection_witness: CW, + list: &mut Whitelist, ) { - vec_set::insert( - &mut list.collections, - object::uid_to_inner(collection) - ); + vec_set::insert(&mut list.collections, type_into_string()); } /// Any collection is allowed to remove itself from any whitelist at any @@ -38,56 +38,57 @@ module nft_protocol::transfer_whitelist { /// /// It's always the creator's right to decide at any point what authorities /// can transfer NFTs of that collection. - public fun remove_itself(collection: &UID, list: &mut Whitelist) { - vec_set::remove( - &mut list.collections, - object::uid_as_inner(collection) - ); + public fun remove_itself( + _collection_witness: CW, + list: &mut Whitelist, + ) { + vec_set::remove(&mut list.collections, &type_into_string()); } /// The whitelist owner can remove any collection at any point. - public fun remove_collection( - _witness: W, - collection: &ID, - list: &mut Whitelist, + public fun remove_collection( + _whitelist_witness: WW, + collection_witness_type: &String, + list: &mut Whitelist, ) { - vec_set::remove(&mut list.collections, collection); + vec_set::remove(&mut list.collections, collection_witness_type); } - /// To insert a new entity into a list we need confirmation by the whitelist + /// To insert a new authority into a list we need confirmation by the whitelist /// authority (via witness.) - public fun insert_entity( - _witness: W, - entity: &ID, - list: &mut Whitelist, + public fun insert_authority( + _whitelist_witness: WW, + authority_witness_type: String, + list: &mut Whitelist, ) { if (option::is_none(&list.authorities)) { - list.authorities = option::some(vec_set::singleton(*entity)); + list.authorities = option::some( + vec_set::singleton(authority_witness_type) + ); } else { vec_set::insert( option::borrow_mut(&mut list.authorities), - *entity, + authority_witness_type, ); } } /// The whitelist authority (via witness) can at any point remove any - /// entity from their list. - public fun remove_entity( - _witness: W, - entity: &ID, - list: &mut Whitelist, + /// authority from their list. + public fun remove_authority( + _whitelist_witness: WW, + authority_witness_type: &String, + list: &mut Whitelist, ) { vec_set::remove( option::borrow_mut(&mut list.authorities), - entity, + authority_witness_type, ); } - public fun can_be_transferred( - collection: &ID, - authority: &ID, - whitelist: &Whitelist, + public fun can_be_transferred( + _authority_witness: Auth, + whitelist: &Whitelist, ): bool { if (option::is_none(&whitelist.authorities)) { return true @@ -95,7 +96,11 @@ module nft_protocol::transfer_whitelist { let e = option::borrow(&whitelist.authorities); - vec_set::contains(e, authority) && - vec_set::contains(&whitelist.collections, collection) + vec_set::contains(e, &type_into_string()) && + vec_set::contains(&whitelist.collections, &type_into_string()) + } + + fun type_into_string(): String { + type_name::into_string(type_name::get()) } } From 3c969399fbf208a3c689f0e51b61deef7f7a964b Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 31 Oct 2022 14:07:34 +0100 Subject: [PATCH 15/35] Adding some docs about the 3 witness types --- sources/safe/transfer_whitelist.move | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index b12d13c8..b9c0beb5 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -1,4 +1,17 @@ module nft_protocol::transfer_whitelist { + //! Whitelists NFT transfers. + //! + //! Three generics at play: + //! 1. WW (whitelist witness) enables any organization to start their own + //! whitelist and manage it according to their own rules; + //! 2. CW (collection witness) enpowers creators to add or remove their + //! collections to whitelists; + //! 3. Auth (3rd party witness) is used to authorize contracts via their + //! witness types. If e.g. an orderbook trading contract wants to be + //! included in a whitelist, the whitelist admin adds the stringified + //! version of their witness type. The OB then uses this witness type + //! to authorize transfers. + use std::option::{Self, Option}; use std::type_name; use sui::vec_set::{Self, VecSet}; @@ -19,7 +32,7 @@ module nft_protocol::transfer_whitelist { } /// To add a collection to the list, we need a confirmation by both the - /// whitelist authority (via witness) and the collection creator (via &UID.) + /// whitelist authority and the collection creator via witness pattern. /// /// If the whitelist authority wants to enable any creator to add their /// collection to the whitelist, they can reexport this function in their @@ -54,8 +67,8 @@ module nft_protocol::transfer_whitelist { vec_set::remove(&mut list.collections, collection_witness_type); } - /// To insert a new authority into a list we need confirmation by the whitelist - /// authority (via witness.) + /// To insert a new authority into a list we need confirmation by the + /// whitelist authority (via witness.) public fun insert_authority( _whitelist_witness: WW, authority_witness_type: String, @@ -86,6 +99,8 @@ module nft_protocol::transfer_whitelist { ); } + /// Checks whether given authority witness is in the whitelist, and also + /// whether given collection witness (CW) is in the whitelist. public fun can_be_transferred( _authority_witness: Auth, whitelist: &Whitelist, From 81cd2f592c805d59b8169ffff16905284220812b Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 31 Oct 2022 16:45:14 +0100 Subject: [PATCH 16/35] Renaming generic, using store with Whitelist and creating a struct instance instead of sharing object --- sources/safe/transfer_whitelist.move | 53 +++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index b9c0beb5..5c5f0094 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -2,7 +2,7 @@ module nft_protocol::transfer_whitelist { //! Whitelists NFT transfers. //! //! Three generics at play: - //! 1. WW (whitelist witness) enables any organization to start their own + //! 1. Admin (whitelist witness) enables any organization to start their own //! whitelist and manage it according to their own rules; //! 2. CW (collection witness) enpowers creators to add or remove their //! collections to whitelists; @@ -12,13 +12,15 @@ module nft_protocol::transfer_whitelist { //! version of their witness type. The OB then uses this witness type //! to authorize transfers. + use std::ascii::String; use std::option::{Self, Option}; use std::type_name; - use sui::vec_set::{Self, VecSet}; + use sui::object; use sui::object::UID; - use std::ascii::String; + use sui::tx_context::TxContext; + use sui::vec_set::{Self, VecSet}; - struct Whitelist has key { + struct Whitelist has key, store { id: UID, /// Which collections does this whitelist apply to? /// @@ -31,6 +33,17 @@ module nft_protocol::transfer_whitelist { authorities: Option>, } + public fun create( + _witness: Admin, + ctx: &mut TxContext + ): Whitelist { + Whitelist { + id: object::new(ctx), + collections: vec_set::empty(), + authorities: option::none(), + } + } + /// To add a collection to the list, we need a confirmation by both the /// whitelist authority and the collection creator via witness pattern. /// @@ -38,10 +51,10 @@ module nft_protocol::transfer_whitelist { /// collection to the whitelist, they can reexport this function in their /// module without the witness protection. However, we opt for witness /// collection to give the whitelist owner a way to combat spam. - public fun insert_collection( - _whitelist_witness: WW, + public fun insert_collection( + _whitelist_witness: Admin, _collection_witness: CW, - list: &mut Whitelist, + list: &mut Whitelist, ) { vec_set::insert(&mut list.collections, type_into_string()); } @@ -51,28 +64,28 @@ module nft_protocol::transfer_whitelist { /// /// It's always the creator's right to decide at any point what authorities /// can transfer NFTs of that collection. - public fun remove_itself( + public fun remove_itself( _collection_witness: CW, - list: &mut Whitelist, + list: &mut Whitelist, ) { vec_set::remove(&mut list.collections, &type_into_string()); } /// The whitelist owner can remove any collection at any point. - public fun remove_collection( - _whitelist_witness: WW, + public fun remove_collection( + _whitelist_witness: Admin, collection_witness_type: &String, - list: &mut Whitelist, + list: &mut Whitelist, ) { vec_set::remove(&mut list.collections, collection_witness_type); } /// To insert a new authority into a list we need confirmation by the /// whitelist authority (via witness.) - public fun insert_authority( - _whitelist_witness: WW, + public fun insert_authority( + _whitelist_witness: Admin, authority_witness_type: String, - list: &mut Whitelist, + list: &mut Whitelist, ) { if (option::is_none(&list.authorities)) { list.authorities = option::some( @@ -88,10 +101,10 @@ module nft_protocol::transfer_whitelist { /// The whitelist authority (via witness) can at any point remove any /// authority from their list. - public fun remove_authority( - _whitelist_witness: WW, + public fun remove_authority( + _whitelist_witness: Admin, authority_witness_type: &String, - list: &mut Whitelist, + list: &mut Whitelist, ) { vec_set::remove( option::borrow_mut(&mut list.authorities), @@ -101,9 +114,9 @@ module nft_protocol::transfer_whitelist { /// Checks whether given authority witness is in the whitelist, and also /// whether given collection witness (CW) is in the whitelist. - public fun can_be_transferred( + public fun can_be_transferred( _authority_witness: Auth, - whitelist: &Whitelist, + whitelist: &Whitelist, ): bool { if (option::is_none(&whitelist.authorities)) { return true From 68d403f8b20ae1450278b76a1892153b6c6cff9b Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 31 Oct 2022 17:19:53 +0100 Subject: [PATCH 17/35] Asserting that tx sender is a collection creator before adding collection into a whitelist --- sources/collection/collection.move | 26 +++++++++++++++++---- sources/safe/transfer_whitelist.move | 35 ++++++++++++++++++++++------ sources/utils/err.move | 4 ++++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/sources/collection/collection.move b/sources/collection/collection.move index 5f20ca4a..e2ad9bcf 100644 --- a/sources/collection/collection.move +++ b/sources/collection/collection.move @@ -537,8 +537,8 @@ module nft_protocol::collection { /// Get the Collections's `tags` public fun tags( collection: &Collection, - ): Tags { - collection.tags + ): &Tags { + &collection.tags } /// Get the Collection's `is_mutable` @@ -558,8 +558,8 @@ module nft_protocol::collection { /// Get the Collection's `creators` public fun creators( collection: &Collection, - ): vector { - collection.creators + ): &vector { + &collection.creators } /// Get an immutable reference to Collections's `cap` @@ -602,6 +602,24 @@ module nft_protocol::collection { mint.collection_id } + // === Utility Function === + + public fun is_creator( + who: address, + creators: &vector, + ): bool { + let i = 0; + while (i < vector::length(creators)) { + let creator = vector::borrow(creators, i); + if (creator.creator_address == who) { + return true + }; + i = i + 1; + }; + + false + } + // === Private Functions === fun create_mint_authority( diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index 5c5f0094..38b981bd 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -12,12 +12,14 @@ module nft_protocol::transfer_whitelist { //! version of their witness type. The OB then uses this witness type //! to authorize transfers. + use nft_protocol::collection::{Self, Collection}; + use nft_protocol::err; use std::ascii::String; use std::option::{Self, Option}; use std::type_name; use sui::object; use sui::object::UID; - use sui::tx_context::TxContext; + use sui::tx_context::{Self, TxContext}; use sui::vec_set::{Self, VecSet}; struct Whitelist has key, store { @@ -51,12 +53,15 @@ module nft_protocol::transfer_whitelist { /// collection to the whitelist, they can reexport this function in their /// module without the witness protection. However, we opt for witness /// collection to give the whitelist owner a way to combat spam. - public fun insert_collection( + public fun insert_collection( _whitelist_witness: Admin, - _collection_witness: CW, + collection: &Collection, list: &mut Whitelist, + ctx: &mut TxContext, ) { - vec_set::insert(&mut list.collections, type_into_string()); + assert_is_creator(collection, ctx); + + vec_set::insert(&mut list.collections, type_into_string()); } /// Any collection is allowed to remove itself from any whitelist at any @@ -64,11 +69,14 @@ module nft_protocol::transfer_whitelist { /// /// It's always the creator's right to decide at any point what authorities /// can transfer NFTs of that collection. - public fun remove_itself( - _collection_witness: CW, + public fun remove_itself( + collection: &Collection, list: &mut Whitelist, + ctx: &mut TxContext, ) { - vec_set::remove(&mut list.collections, &type_into_string()); + assert_is_creator(collection, ctx); + + vec_set::remove(&mut list.collections, &type_into_string()); } /// The whitelist owner can remove any collection at any point. @@ -131,4 +139,17 @@ module nft_protocol::transfer_whitelist { fun type_into_string(): String { type_name::into_string(type_name::get()) } + + fun assert_is_creator( + collection: &Collection, + ctx: &mut TxContext, + ) { + assert!( + collection::is_creator( + tx_context::sender(ctx), + collection::creators(collection), + ), + err::sender_not_collection_creator(), + ); + } } diff --git a/sources/utils/err.move b/sources/utils/err.move index f0f7b757..c5185744 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -135,4 +135,8 @@ module nft_protocol::err { public fun authority_not_whitelisted(): u64 { return Prefix + 400 } + + public fun sender_not_collection_creator(): u64 { + return Prefix + 401 + } } From 6ea12b653c4fbb929568efb15503b33a84bbffd7 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Tue, 1 Nov 2022 13:11:33 +0100 Subject: [PATCH 18/35] Using TypeName instead of String because types can always be referenced --- sources/safe/transfer_whitelist.move | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index 38b981bd..e47024d7 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -14,9 +14,8 @@ module nft_protocol::transfer_whitelist { use nft_protocol::collection::{Self, Collection}; use nft_protocol::err; - use std::ascii::String; use std::option::{Self, Option}; - use std::type_name; + use std::type_name::{Self, TypeName}; use sui::object; use sui::object::UID; use sui::tx_context::{Self, TxContext}; @@ -26,13 +25,13 @@ module nft_protocol::transfer_whitelist { id: UID, /// Which collections does this whitelist apply to? /// - /// This is a string gotten from `type_name::get` function - collections: VecSet, + /// We use reflection to avoid generics. + collections: VecSet, /// If None, then there's no whitelist and everyone is allowed. /// /// Otherwise we use a witness pattern but store the witness object as /// the output of `type_name::get`. - authorities: Option>, + authorities: Option>, } public fun create( @@ -61,7 +60,7 @@ module nft_protocol::transfer_whitelist { ) { assert_is_creator(collection, ctx); - vec_set::insert(&mut list.collections, type_into_string()); + vec_set::insert(&mut list.collections, type_name::get()); } /// Any collection is allowed to remove itself from any whitelist at any @@ -76,47 +75,52 @@ module nft_protocol::transfer_whitelist { ) { assert_is_creator(collection, ctx); - vec_set::remove(&mut list.collections, &type_into_string()); + vec_set::remove(&mut list.collections, &type_name::get()); } /// The whitelist owner can remove any collection at any point. - public fun remove_collection( + public fun remove_collection( _whitelist_witness: Admin, - collection_witness_type: &String, list: &mut Whitelist, ) { - vec_set::remove(&mut list.collections, collection_witness_type); + vec_set::remove(&mut list.collections, &type_name::get()); + } + + /// Removes all collections from this list. + public fun clear_collections( + _whitelist_witness: Admin, + list: &mut Whitelist, + ) { + list.collections = vec_set::empty(); } /// To insert a new authority into a list we need confirmation by the /// whitelist authority (via witness.) - public fun insert_authority( + public fun insert_authority( _whitelist_witness: Admin, - authority_witness_type: String, list: &mut Whitelist, ) { if (option::is_none(&list.authorities)) { list.authorities = option::some( - vec_set::singleton(authority_witness_type) + vec_set::singleton(type_name::get()) ); } else { vec_set::insert( option::borrow_mut(&mut list.authorities), - authority_witness_type, + type_name::get(), ); } } /// The whitelist authority (via witness) can at any point remove any /// authority from their list. - public fun remove_authority( + public fun remove_authority( _whitelist_witness: Admin, - authority_witness_type: &String, list: &mut Whitelist, ) { vec_set::remove( option::borrow_mut(&mut list.authorities), - authority_witness_type, + &type_name::get(), ); } @@ -132,12 +136,8 @@ module nft_protocol::transfer_whitelist { let e = option::borrow(&whitelist.authorities); - vec_set::contains(e, &type_into_string()) && - vec_set::contains(&whitelist.collections, &type_into_string()) - } - - fun type_into_string(): String { - type_name::into_string(type_name::get()) + vec_set::contains(e, &type_name::get()) && + vec_set::contains(&whitelist.collections, &type_name::get()) } fun assert_is_creator( From 4d13a16249b90cedacb03967668301e6582aad33 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Tue, 1 Nov 2022 13:57:37 +0100 Subject: [PATCH 19/35] More granular control over what NFTs can be deposited --- sources/nft/nft.move | 16 ++++ sources/safe/safe.move | 173 +++++++++++++++++++++++++++++++++-------- sources/utils/err.move | 4 + 3 files changed, 162 insertions(+), 31 deletions(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index 05038a16..b2e3ac6c 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -185,6 +185,22 @@ module nft_protocol::nft { transfer::transfer(nft, target); } + public fun change_logical_owner( + nft: &mut Nft, + target: address, + authority: Auth, + whitelist: &Whitelist, + ) { + + let is_ok = transfer_whitelist::can_be_transferred( + authority, + whitelist, + ); + assert!(is_ok, err::authority_not_whitelisted()); + + nft.logical_owner = target; + } + public fun transfer_to_owner(nft: Nft) { let logical_owner = nft.logical_owner; transfer::transfer(nft, logical_owner); diff --git a/sources/safe/safe.move b/sources/safe/safe.move index 4f7269cd..88f9ec9b 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -1,18 +1,29 @@ module nft_protocol::safe { - use sui::object::{ID, UID}; - use sui::vec_map::{Self, VecMap}; - use sui::tx_context::{Self, TxContext}; - use sui::object; - use sui::transfer::{share_object, transfer}; use nft_protocol::err; use nft_protocol::nft::{Self, Nft}; use nft_protocol::transfer_whitelist::Whitelist; + use sui::vec_set::{Self, VecSet}; + use sui::event; use sui::object_bag::{Self, ObjectBag}; + use sui::object; + use sui::object::{ID, UID}; + use sui::transfer::{share_object, transfer}; + use sui::tx_context::{Self, TxContext}; + use sui::vec_map::{Self, VecMap}; + use std::type_name::{Self, TypeName}; struct Safe has key { id: UID, refs: VecMap, nfts: ObjectBag, + /// If set to false, the owner can select which collections can be + /// deposited to the safe. + accepts_any_deposit: bool, + /// If the flag `accepts_any_deposit` is set to false, then we check + /// whether a collection is stored in this set. + /// + /// Enables more granular control over NFTs to combat spam. + enabled_deposits: VecSet, } /// Keeps info about an NFT which enables us to issue transfer caps etc. @@ -37,12 +48,6 @@ module nft_protocol::safe { safe: ID, } - /// Enables the owner to deposit NFTs into the `Safe`. - struct DepositCap has key, store { - id: UID, - safe: ID, - } - /// Enables the owner to transfer given NFT out of the `Safe`. struct TransferCap has key, store { id: UID, @@ -60,6 +65,16 @@ module nft_protocol::safe { is_exlusive: bool, } + struct DepositEvent has copy, drop { + safe: ID, + nft: ID, + } + + struct TransferEvent has copy, drop { + safe: ID, + nft: ID, + } + /// Instantiates a new shared object `Safe` and transfer `OwnerCap` to the /// tx sender. public entry fun create_for_sender(ctx: &mut TxContext) { @@ -130,28 +145,62 @@ module nft_protocol::safe { } } - /// Transfer an NFT from the logical owner to the `Safe`. - public fun deposit_nft( - nft: Nft, - deposit_cap: &DepositCap, + /// The owner can restrict deposits into the `Safe` from other users. + public entry fun toggle_accepts_any_deposit( + owner_cap: &OwnerCap, + safe: &mut Safe, + ) { + assert_owner_cap(owner_cap, safe); + + safe.accepts_any_deposit = !safe.accepts_any_deposit; + } + + /// The owner can restrict deposits into the `Safe` from given collection. + /// + /// However, if the flag `Safe::accepts_any_deposit` is set to true, then + /// that takes precedence. + public entry fun toggle_deposits_of_collection( + owner_cap: &OwnerCap, + safe: &mut Safe, + ) { + assert_owner_cap(owner_cap, safe); + + let col_type = type_name::get(); + if (vec_set::contains(&safe.enabled_deposits, &col_type)) { + vec_set::remove(&mut safe.enabled_deposits, &col_type); + } else { + vec_set::insert(&mut safe.enabled_deposits, col_type); + }; + } + + /// Transfer an NFT into the `Safe`. + /// + /// Requires that `accepts_any_deposit` flag is set to true, or that the + /// `Safe` owner enabled NFTs of given collection to be inserted. + public entry fun deposit_nft( + nft: Nft, safe: &mut Safe, ctx: &mut TxContext, ) { - assert_deposit_cap(deposit_cap, safe); + assert_can_deposit(safe); - let nft_id = object::id(&nft); + deposit_nft_(nft, safe, ctx); + } - vec_map::insert(&mut safe.refs, nft_id, NftRef { - version: new_id(ctx), - transfer_cap_counter: 0, - is_exlusively_listed: false, - }); + /// Transfer an NFT from owner to the `Safe`. + public entry fun deposit_nft_priviledged( + nft: Nft, + owner_cap: &OwnerCap, + safe: &mut Safe, + ctx: &mut TxContext, + ) { + assert_owner_cap(owner_cap, safe); - object_bag::add(&mut safe.nfts, nft_id, nft); + deposit_nft_(nft, safe, ctx); } /// Remove an NFT from the `Safe` and give it back to the logical owner. - public fun withdraw_nft( + public entry fun withdraw_nft( nft_id: ID, owner_cap: &OwnerCap, safe: &mut Safe, @@ -173,7 +222,7 @@ module nft_protocol::safe { /// If the NFT is not exlusively listed, it can happen that the transfer /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. - public fun transfer_nft( + public fun transfer_nft_to_recipient( nft_id: ID, transfer_cap: TransferCap, recipient: address, @@ -201,6 +250,45 @@ module nft_protocol::safe { nft::transfer(nft, recipient, authority, whitelist); } + /// Use a transfer cap to get an NFT out of source `Safe` and deposit it + /// to the target `Safe`. The recipient address should match the owner of + /// the target `Safe`. + /// + /// If the NFT is not exlusively listed, it can happen that the transfer + /// cap is no longer valid. The NFT could've been traded or the trading cap + /// revoked. + public fun transfer_nft_to_safe( + nft_id: ID, + transfer_cap: TransferCap, + recipient: address, + authority: Auth, + whitelist: &Whitelist, + source: &mut Safe, + target: &mut Safe, + ctx: &mut TxContext, + ) { + assert_transfer_cap_of_safe(&transfer_cap, source); + assert_nft_of_transfer_cap(&nft_id, &transfer_cap); + assert_contains_nft(&nft_id, source); + + let (_, ref) = vec_map::remove(&mut source.refs, &nft_id); + assert_version_match(&ref, &transfer_cap); + + let TransferCap { + id, + safe: _, + nft: _, + version: _, + is_exlusive: _, + } = transfer_cap; + object::delete(id); + + let nft = object_bag::remove>(&mut source.nfts, nft_id); + nft::change_logical_owner(&mut nft, recipient, authority, whitelist); + + deposit_nft(nft, target, ctx); + } + /// Destroys given transfer cap. This is mainly useful for exlusively listed /// NFTs. public fun burn_transfer_cap( @@ -252,6 +340,8 @@ module nft_protocol::safe { id: object::new(ctx), refs: vec_map::empty(), nfts: object_bag::new(ctx), + accepts_any_deposit: true, + enabled_deposits: vec_set::empty(), }; let cap = OwnerCap { id: object::new(ctx), @@ -270,14 +360,30 @@ module nft_protocol::safe { new_id } + fun deposit_nft_( + nft: Nft, + safe: &mut Safe, + ctx: &mut TxContext, + ) { + let nft_id = object::id(&nft); + + vec_map::insert(&mut safe.refs, nft_id, NftRef { + version: new_id(ctx), + transfer_cap_counter: 0, + is_exlusively_listed: false, + }); + + object_bag::add(&mut safe.nfts, nft_id, nft); + } + // === Getters === public fun owner_cap_safe(cap: &OwnerCap): ID { cap.safe } - public fun deposit_cap_safe(cap: &DepositCap): ID { - cap.safe + public fun accepts_any_deposit(safe: &Safe): bool { + safe.accepts_any_deposit } public fun transfer_cap_safe(cap: &TransferCap): ID { @@ -299,10 +405,6 @@ module nft_protocol::safe { assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); } - public fun assert_deposit_cap(cap: &DepositCap, safe: &Safe) { - assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); - } - public fun assert_transfer_cap_of_safe(cap: &TransferCap, safe: &Safe) { assert!(cap.safe == object::id(safe), err::safe_cap_mismatch()); } @@ -324,4 +426,13 @@ module nft_protocol::safe { public fun assert_version_match(ref: &NftRef, cap: &TransferCap) { assert!(ref.version == cap.version, err::transfer_cap_expired()); } + + public fun assert_can_deposit(safe: &Safe) { + if (!safe.accepts_any_deposit) { + assert!( + vec_set::contains(&safe.enabled_deposits, &type_name::get()), + err::safe_does_not_accept_deposits(), + ); + } + } } diff --git a/sources/utils/err.move b/sources/utils/err.move index c5185744..ebaabdf2 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -130,6 +130,10 @@ module nft_protocol::err { return Prefix + 304 } + public fun safe_does_not_accept_deposits(): u64 { + return Prefix + 305 + } + // === Whitelist === public fun authority_not_whitelisted(): u64 { From 58ccc98cb64a16dbf05cc6d25bceae4d8f44ebb8 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Tue, 1 Nov 2022 14:03:51 +0100 Subject: [PATCH 20/35] Deduplicating logic for transfers --- sources/safe/safe.move | 83 +++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index 88f9ec9b..117434d5 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -230,23 +230,8 @@ module nft_protocol::safe { whitelist: &Whitelist, safe: &mut Safe, ) { - assert_transfer_cap_of_safe(&transfer_cap, safe); - assert_nft_of_transfer_cap(&nft_id, &transfer_cap); - assert_contains_nft(&nft_id, safe); - - let (_, ref) = vec_map::remove(&mut safe.refs, &nft_id); - assert_version_match(&ref, &transfer_cap); - - let TransferCap { - id, - safe: _, - nft: _, - version: _, - is_exlusive: _, - } = transfer_cap; - object::delete(id); + let nft = get_nft_for_transfer_(nft_id, transfer_cap, safe); - let nft = object_bag::remove>(&mut safe.nfts, nft_id); nft::transfer(nft, recipient, authority, whitelist); } @@ -267,31 +252,15 @@ module nft_protocol::safe { target: &mut Safe, ctx: &mut TxContext, ) { - assert_transfer_cap_of_safe(&transfer_cap, source); - assert_nft_of_transfer_cap(&nft_id, &transfer_cap); - assert_contains_nft(&nft_id, source); - - let (_, ref) = vec_map::remove(&mut source.refs, &nft_id); - assert_version_match(&ref, &transfer_cap); - - let TransferCap { - id, - safe: _, - nft: _, - version: _, - is_exlusive: _, - } = transfer_cap; - object::delete(id); + let nft = get_nft_for_transfer_(nft_id, transfer_cap, source); - let nft = object_bag::remove>(&mut source.nfts, nft_id); nft::change_logical_owner(&mut nft, recipient, authority, whitelist); - deposit_nft(nft, target, ctx); } /// Destroys given transfer cap. This is mainly useful for exlusively listed /// NFTs. - public fun burn_transfer_cap( + public entry fun burn_transfer_cap( transfer_cap: TransferCap, safe: &mut Safe, ) { @@ -319,16 +288,16 @@ module nft_protocol::safe { /// `TransferCap` objects. /// /// Can happen only if the NFT is not listed exlusively. - public fun delist_nft( - nft: &ID, + public entry fun delist_nft( + nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, ctx: &mut TxContext, ) { assert_owner_cap(owner_cap, safe); - assert_contains_nft(nft, safe); + assert_contains_nft(&nft, safe); - let ref = vec_map::get_mut(&mut safe.refs, nft); + let ref = vec_map::get_mut(&mut safe.refs, &nft); assert_not_exlusively_listed(ref); ref.version = new_id(ctx); @@ -374,6 +343,44 @@ module nft_protocol::safe { }); object_bag::add(&mut safe.nfts, nft_id, nft); + + event::emit( + DepositEvent { + safe: object::id(safe), + nft: nft_id, + } + ); + } + + fun get_nft_for_transfer_( + nft_id: ID, + transfer_cap: TransferCap, + safe: &mut Safe, + ): Nft { + event::emit( + TransferEvent { + safe: object::id(safe), + nft: nft_id, + } + ); + + assert_transfer_cap_of_safe(&transfer_cap, safe); + assert_nft_of_transfer_cap(&nft_id, &transfer_cap); + assert_contains_nft(&nft_id, safe); + + let (_, ref) = vec_map::remove(&mut safe.refs, &nft_id); + assert_version_match(&ref, &transfer_cap); + + let TransferCap { + id, + safe: _, + nft: _, + version: _, + is_exlusive: _, + } = transfer_cap; + object::delete(id); + + object_bag::remove>(&mut safe.nfts, nft_id) } // === Getters === From 0fa8fb1aa19d80b7fd2744634b1e70676a3e707e Mon Sep 17 00:00:00 2001 From: porkbrain Date: Tue, 1 Nov 2022 14:08:39 +0100 Subject: [PATCH 21/35] Adding docs to transfer methods in Nft wrapper module --- sources/nft/nft.move | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index b2e3ac6c..41d70f7c 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -170,28 +170,25 @@ module nft_protocol::nft { // === Transfer Functions === + /// If the authority was whitelisted by the creator, we transfer + /// the NFT to the recipient address. public fun transfer( nft: Nft, target: address, authority: Auth, whitelist: &Whitelist, ) { - let is_ok = transfer_whitelist::can_be_transferred( - authority, - whitelist, - ); - assert!(is_ok, err::authority_not_whitelisted()); - + change_logical_owner(&mut nft, target, authority, whitelist); transfer::transfer(nft, target); } + /// Whitelisted contracts (by creator) can change logical owner of an NFT. public fun change_logical_owner( nft: &mut Nft, target: address, authority: Auth, whitelist: &Whitelist, ) { - let is_ok = transfer_whitelist::can_be_transferred( authority, whitelist, @@ -201,6 +198,7 @@ module nft_protocol::nft { nft.logical_owner = target; } + /// Clawing back an NFT is always possible. public fun transfer_to_owner(nft: Nft) { let logical_owner = nft.logical_owner; transfer::transfer(nft, logical_owner); From 3d615885d5c99401bae2487c0b2d3c178b3cd061 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Tue, 1 Nov 2022 14:09:12 +0100 Subject: [PATCH 22/35] Being consistent with variable naming: target -> recipient --- sources/nft/nft.move | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index 41d70f7c..194060ec 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -174,18 +174,18 @@ module nft_protocol::nft { /// the NFT to the recipient address. public fun transfer( nft: Nft, - target: address, + recipient: address, authority: Auth, whitelist: &Whitelist, ) { - change_logical_owner(&mut nft, target, authority, whitelist); - transfer::transfer(nft, target); + change_logical_owner(&mut nft, recipient, authority, whitelist); + transfer::transfer(nft, recipient); } /// Whitelisted contracts (by creator) can change logical owner of an NFT. public fun change_logical_owner( nft: &mut Nft, - target: address, + recipient: address, authority: Auth, whitelist: &Whitelist, ) { @@ -195,7 +195,7 @@ module nft_protocol::nft { ); assert!(is_ok, err::authority_not_whitelisted()); - nft.logical_owner = target; + nft.logical_owner = recipient; } /// Clawing back an NFT is always possible. From 0baa5432c20e5b72a8c40a04585ca3f39c2b9c29 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Tue, 1 Nov 2022 14:20:11 +0100 Subject: [PATCH 23/35] Adding docs and renaming some fields --- sources/safe/safe.move | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index 117434d5..673b6908 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -14,7 +14,10 @@ module nft_protocol::safe { struct Safe has key { id: UID, + /// Accounting for deposited NFTs. Each NFT in the object bag is + /// represented in this map. refs: VecMap, + /// Holds the actual NFT objects as child objects. nfts: ObjectBag, /// If set to false, the owner can select which collections can be /// deposited to the safe. @@ -23,7 +26,7 @@ module nft_protocol::safe { /// whether a collection is stored in this set. /// /// Enables more granular control over NFTs to combat spam. - enabled_deposits: VecSet, + collections_with_enabled_deposits: VecSet, } /// Keeps info about an NFT which enables us to issue transfer caps etc. @@ -91,7 +94,7 @@ module nft_protocol::safe { /// /// Otherwise, there's a risk of a race condition as multiple non-exclusive /// transfer caps can be created. - public fun create_transfer_cap( + public entry fun create_transfer_cap( nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, @@ -120,7 +123,7 @@ module nft_protocol::safe { /// Creates an irrevocable and exclusive transfer cap. /// /// Useful for trading contracts which cannot claim an NFT atomically. - public fun create_exlusive_transfer_cap( + public entry fun create_exlusive_transfer_cap( nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, @@ -166,10 +169,10 @@ module nft_protocol::safe { assert_owner_cap(owner_cap, safe); let col_type = type_name::get(); - if (vec_set::contains(&safe.enabled_deposits, &col_type)) { - vec_set::remove(&mut safe.enabled_deposits, &col_type); + if (vec_set::contains(&safe.collections_with_enabled_deposits, &col_type)) { + vec_set::remove(&mut safe.collections_with_enabled_deposits, &col_type); } else { - vec_set::insert(&mut safe.enabled_deposits, col_type); + vec_set::insert(&mut safe.collections_with_enabled_deposits, col_type); }; } @@ -310,7 +313,7 @@ module nft_protocol::safe { refs: vec_map::empty(), nfts: object_bag::new(ctx), accepts_any_deposit: true, - enabled_deposits: vec_set::empty(), + collections_with_enabled_deposits: vec_set::empty(), }; let cap = OwnerCap { id: object::new(ctx), @@ -437,7 +440,7 @@ module nft_protocol::safe { public fun assert_can_deposit(safe: &Safe) { if (!safe.accepts_any_deposit) { assert!( - vec_set::contains(&safe.enabled_deposits, &type_name::get()), + vec_set::contains(&safe.collections_with_enabled_deposits, &type_name::get()), err::safe_does_not_accept_deposits(), ); } From d1f0270a6d2406ee4b2c97c918edbbc0ad9854cf Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Tue, 1 Nov 2022 17:19:47 +0000 Subject: [PATCH 24/35] New flow for embedded NFTs --- sources/launchpad/market/fixed_price.move | 16 ++++++++++-- sources/launchpad/sale.move | 12 ++++++++- sources/launchpad/slingshot.move | 32 ++++++++++++++++++----- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/sources/launchpad/market/fixed_price.move b/sources/launchpad/market/fixed_price.move index 7192d371..5a67aab7 100644 --- a/sources/launchpad/market/fixed_price.move +++ b/sources/launchpad/market/fixed_price.move @@ -159,6 +159,7 @@ module nft_protocol::fixed_price { let launchpad_id = slingshot::id(slingshot); let receiver = slingshot::receiver(slingshot); + let collection_id = slingshot::collection_id(slingshot); let sale = slingshot::sale_mut(slingshot, tier_index); // Infer that sales is NOT whitelisted @@ -179,7 +180,12 @@ module nft_protocol::fixed_price { ctx ); - let certificate = sale::issue_nft_certificate(sale, launchpad_id, ctx); + let certificate = sale::issue_nft_certificate( + sale, + launchpad_id, + collection_id, + ctx + ); transfer::transfer( certificate, @@ -206,6 +212,7 @@ module nft_protocol::fixed_price { let launchpad_id = slingshot::id(slingshot); let receiver = slingshot::receiver(slingshot); + let collection_id = slingshot::collection_id(slingshot); let sale = slingshot::sale_mut(slingshot, tier_index); // Infer that sales is whitelisted @@ -234,7 +241,12 @@ module nft_protocol::fixed_price { whitelist::burn_whitelist_token(whitelist_token); - let certificate = sale::issue_nft_certificate(sale, launchpad_id, ctx); + let certificate = sale::issue_nft_certificate( + sale, + launchpad_id, + collection_id, + ctx + ); transfer::transfer( certificate, diff --git a/sources/launchpad/sale.move b/sources/launchpad/sale.move index c486c9f3..1854f0e9 100644 --- a/sources/launchpad/sale.move +++ b/sources/launchpad/sale.move @@ -36,6 +36,7 @@ module nft_protocol::sale { struct NftCertificate has key, store { id: UID, launchpad_id: ID, + collection_id: ID, nft_id: ID, } @@ -91,13 +92,15 @@ module nft_protocol::sale { public fun issue_nft_certificate( sale: &mut Sale, launchpad_id: ID, + collection_id: ID, ctx: &mut TxContext, ): NftCertificate { let nft_id = pop_nft(sale); let certificate = NftCertificate { id: object::new(ctx), - launchpad_id: launchpad_id, + launchpad_id, + collection_id, nft_id: nft_id, }; @@ -110,6 +113,7 @@ module nft_protocol::sale { let NftCertificate { id, launchpad_id: _, + collection_id: _, nft_id: _, } = certificate; @@ -176,4 +180,10 @@ module nft_protocol::sale { ): bool { sale.whitelisted } + + public fun collection_id( + certificate: &NftCertificate, + ): ID { + certificate.collection_id + } } diff --git a/sources/launchpad/slingshot.move b/sources/launchpad/slingshot.move index 24b3832f..87743494 100644 --- a/sources/launchpad/slingshot.move +++ b/sources/launchpad/slingshot.move @@ -13,7 +13,7 @@ module nft_protocol::slingshot { use sui::object::{Self, ID , UID}; use sui::tx_context::{Self, TxContext}; - use nft_protocol::nft::{Self, Nft}; + use nft_protocol::nft; use nft_protocol::err; use nft_protocol::sale::{Self, Sale, NftCertificate}; @@ -126,20 +126,31 @@ module nft_protocol::slingshot { /// of the NFT. Since the slingshot is a shared object anyone can mention it /// in the function signature and therefore be able to mention its child /// objects as well, the NFTs owned by it. - public entry fun claim_nft_embedded( + public entry fun claim_nft_embedded( slingshot: &Slingshot, - nft: Nft, + // TODO: nft_data is a shared object, we should confirm that we can + // add it directly as a parameter since this transfers ownership to the + // function's scope + nft_data: D, certificate: NftCertificate, recipient: address, + ctx: &mut TxContext, ) { assert!( - nft::id(&nft) == sale::nft_id(&certificate), + object::id(&nft_data) == sale::nft_id(&certificate), err::certificate_does_not_correspond_to_nft_given() ); + assert!(is_embedded(slingshot), err::nft_not_embedded()); + + let nft = nft::mint_nft_loose( + object::id(&nft_data), + ctx, + ); + sale::burn_certificate(certificate); - assert!(is_embedded(slingshot), err::nft_not_embedded()); + nft::join_nft_data(&mut nft, nft_data); transfer::transfer( nft, @@ -168,8 +179,6 @@ module nft_protocol::slingshot { err::certificate_does_not_correspond_to_nft_given() ); - sale::burn_certificate(certificate); - assert!(!is_embedded(slingshot), err::nft_not_loose()); // We are currently not increasing the current supply of the NFT @@ -180,6 +189,8 @@ module nft_protocol::slingshot { ctx, ); + sale::burn_certificate(certificate); + transfer::transfer( nft, recipient, @@ -228,6 +239,13 @@ module nft_protocol::slingshot { object::uid_as_inner(&slingshot.id) } + /// Get the Slingshot's `collection_id` + public fun collection_id( + slingshot: &Slingshot, + ): ID { + slingshot.collection_id + } + /// Get the Slingshot's `live` public fun live( slingshot: &Slingshot, From 9c693d361f59ffab76727cdb4d3c545bd91d353c Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Tue, 1 Nov 2022 23:54:07 +0000 Subject: [PATCH 25/35] New NFT release flow --- sources/collection/collection.move | 63 ++---- sources/examples/suimarines.move | 11 +- sources/launchpad/market/fixed_price.move | 4 +- sources/launchpad/sale.move | 19 +- sources/launchpad/slingshot.move | 2 +- sources/nft/nfts/c_nft.move | 224 +++++++++++++++------- sources/nft/nfts/collectible.move | 140 +++++++++++--- sources/nft/nfts/unique_nft.move | 142 ++++++-------- 8 files changed, 364 insertions(+), 241 deletions(-) diff --git a/sources/collection/collection.move b/sources/collection/collection.move index 5f20ca4a..95ee8724 100644 --- a/sources/collection/collection.move +++ b/sources/collection/collection.move @@ -22,7 +22,6 @@ //! frozen, in order to give creators the ability to ammend it prior to //! the primary sale taking place. //! -//! TODO: Consider adding a function `destroy_unregulated`? //! TODO: Consider adding a struct object Collection Proof //! TODO: Verify creator in function to add creator, and function to post verify //! TODO: Split field `is_mutable` to `is_mutable` and `frozen` such that @@ -121,7 +120,7 @@ module nft_protocol::collection { /// Initialises a `MintAuthority` and transfers it to `authority` and - /// initialized `Collection` object and returns it. The `MintAuthority` + /// initializes a `Collection` object and returns it. The `MintAuthority` /// object gives power to the owner to mint objects. There is only one /// `MintAuthority` per `Collection`. The Mint Authority object contains a /// `SupplyPolicy` which can be regulated or unregulated. @@ -189,52 +188,20 @@ module nft_protocol::collection { } } - // TODO: Requires fixing - // /// Burn a Collection with regulated supply object and - // /// returns the Metadata object - // public entry fun burn_regulated( - // collection: Collection, - // mint: MintAuthority, - // ): M { - // assert!( - // supply::current(supply_policy::supply(&mint.supply_policy)) == 0, - // err::supply_is_not_zero() - // ); - - // let MintAuthority { - // id, - // collection_id: _, - // supply_policy, - // } = mint; - - // object::delete(id); - - // event::emit( - // BurnEvent { - // collection_id: id(&collection), - // } - // ); - - // let Collection { - // id, - // name: _, - // description: _, - // symbol: _, - // receiver: _, - // tags: _, - // is_mutable: _, - // royalty_fee_bps: _, - // creators: _, - // mint_authority: _, - // metadata, - // } = collection; - - // supply_policy::destroy_regulated(supply_policy); - - // object::delete(id); - - // metadata - // } + /// Shares the `MintAuthority` object of a given `Collection`. For NFT + /// collections that require users to be the ones to mint the data, one + /// requires the `MintAuthority` to be shared, such that they can access the + /// nft mint functions. + /// + /// An example of this could be a Domain Name Service protocol, which + /// relies on users calling the nft mint function themselses and therefore + /// minting their domain name. + public fun share_authority( + authority: MintAuthority, + _collection: &Collection, + ) { + transfer::share_object(authority); + } /// Make Collections immutable /// WARNING: this is irreversible, use with care diff --git a/sources/examples/suimarines.move b/sources/examples/suimarines.move index e7b32044..8410a685 100644 --- a/sources/examples/suimarines.move +++ b/sources/examples/suimarines.move @@ -4,8 +4,7 @@ module nft_protocol::suimarines { use std::vector; use nft_protocol::collection::{MintAuthority}; - use nft_protocol::fixed_price::{Self, FixedPriceMarket}; - use nft_protocol::slingshot::Slingshot; + use nft_protocol::fixed_price; use nft_protocol::std_collection; use nft_protocol::unique_nft; @@ -43,26 +42,22 @@ module nft_protocol::suimarines { ); } - public entry fun mint_nft( + public entry fun mint_nft_data( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint_authority: &mut MintAuthority, - sale_index: u64, - launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - unique_nft::mint_regulated_nft( + unique_nft::prepare_launchpad_mint( name, description, url, attribute_keys, attribute_values, mint_authority, - sale_index, - launchpad, ctx, ); } diff --git a/sources/launchpad/market/fixed_price.move b/sources/launchpad/market/fixed_price.move index 5a67aab7..3524dd3a 100644 --- a/sources/launchpad/market/fixed_price.move +++ b/sources/launchpad/market/fixed_price.move @@ -286,7 +286,7 @@ module nft_protocol::fixed_price { /// of the launchpad configuration. public entry fun new_price( slingshot: &mut Slingshot, - sale_index: u64, + sale_outlet: u64, new_price: u64, ctx: &mut TxContext, ) { @@ -295,7 +295,7 @@ module nft_protocol::fixed_price { err::wrong_launchpad_admin() ); - let sale = slingshot::sale_mut(slingshot, sale_index); + let sale = slingshot::sale_mut(slingshot, sale_outlet); sale::market_mut(sale).price = new_price; } diff --git a/sources/launchpad/sale.move b/sources/launchpad/sale.move index 1854f0e9..1ed73739 100644 --- a/sources/launchpad/sale.move +++ b/sources/launchpad/sale.move @@ -22,12 +22,18 @@ module nft_protocol::sale { id: UID, tier_index: u64, whitelisted: bool, - // Vector of all IDs owned by the slingshot - nfts: vector, + // Vector of all IDs owned by the slingshot along with a `u64` value + // representing the supply to be minted. + nfts: vector, queue: vector, market: Market, } + struct NftToSell has copy, drop, store { + id: ID, + supply: u64, + } + /// This object acts as an intermediate step between the payment /// and the transfer of the NFT. The user first has to call /// `buy_nft_certificate` which mints and transfers the `NftCertificate` to @@ -95,13 +101,13 @@ module nft_protocol::sale { collection_id: ID, ctx: &mut TxContext, ): NftCertificate { - let nft_id = pop_nft(sale); + let nft_to_sell = pop_nft(sale); let certificate = NftCertificate { id: object::new(ctx), launchpad_id, collection_id, - nft_id: nft_id, + nft_id: nft_to_sell.id, }; certificate @@ -124,9 +130,10 @@ module nft_protocol::sale { public fun add_nft( sale: &mut Sale, id: ID, + supply: u64, ) { let nfts = &mut sale.nfts; - vector::push_back(nfts, id); + vector::push_back(nfts, NftToSell {id, supply}); } /// Pops an NFT's ID from the `nfts` field in `Sale` object @@ -134,7 +141,7 @@ module nft_protocol::sale { /// TODO: Need to push the ID to the queue fun pop_nft( sale: &mut Sale, - ): ID { + ): NftToSell { let nfts = &mut sale.nfts; vector::pop_back(nfts) } diff --git a/sources/launchpad/slingshot.move b/sources/launchpad/slingshot.move index 87743494..6097fc2d 100644 --- a/sources/launchpad/slingshot.move +++ b/sources/launchpad/slingshot.move @@ -169,7 +169,7 @@ module nft_protocol::slingshot { /// objects as well, the NFTs owned by it. public entry fun claim_nft_loose( slingshot: &Slingshot, - nft_data: &D, + nft_data: &mut D, certificate: NftCertificate, recipient: address, ctx: &mut TxContext, diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index fa20ba7b..2db5316d 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -27,6 +27,8 @@ module nft_protocol::c_nft { use nft_protocol::err; use nft_protocol::collection::{Self, MintAuthority}; + use nft_protocol::slingshot::{Self, Slingshot}; + use nft_protocol::sale; use nft_protocol::supply_policy; use nft_protocol::utils::{to_string_vector}; use nft_protocol::supply::{Self, Supply}; @@ -109,9 +111,11 @@ module nft_protocol::c_nft { // === Functions exposed to Witness Module === - /// Mints loose NFT `Composable` data object and shares it. + /// Mints loose NFT `Composable` data and shares it. /// Invokes `mint_and_share_data()`. /// + /// Mints a Composable data object for NFT(s) from a `Collection` + /// with regulated supply /// Mints a Collectible data object for NFT(s) from an unregulated /// `Collection`. /// The only way to mint the NFT data for a collection is to give a @@ -119,24 +123,32 @@ module nft_protocol::c_nft { /// given collection if one is the `MintAuthority` owner. /// /// This function call bootstraps the minting of leaf node NFTs in a - /// Composable collection with unregulated supply. This function does not - /// serve to compose Composable objects, but simply to create the intial - /// objects that are supposed to give rise to the composability tree. + /// Composable collection with regulated supply. This function does + /// not serve to compose Composable objects, but simply to create the + /// intial objects that are supposed to give rise to the composability tree. + /// + /// For a regulated collection with supply of 100 objects, this function + /// will be called in total 100 times to mint such objects. Once these + /// objects are brought to existance the collection creator can start + /// creating composable objects which determine which NFTs can be merged + /// and what the supply of those configurations are. /// /// To be called by the Witness Module deployed by NFT creator. - public fun mint_unregulated_nft_data( + public fun prepare_launchpad_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, max_supply: u64, - mint: &MintAuthority, + mint: &mut MintAuthority, + sale_outlet: u64, + launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - // Assert that it has an uregulated supply policy + // Assert that it has a regulated supply policy assert!( - !supply_policy::regulated(collection::supply_policy(mint)), + supply_policy::regulated(collection::supply_policy(mint)), err::supply_policy_mismatch(), ); @@ -149,38 +161,78 @@ module nft_protocol::c_nft { max_supply, ); + collection::increment_supply(mint, 1); + + let data_uid = object::new(ctx); + let data_id = object::uid_to_inner(&data_uid); + mint_and_share_data( + data_uid, args, collection::mint_collection_id(mint), max_supply, - ctx, + ); + + let sale = slingshot::sale_mut(launchpad, sale_outlet); + + sale::add_nft( + sale, + data_id, + max_supply ); } - /// Mints loose NFT `Composable` data and shares it. - /// Invokes `mint_and_share_data()`. - /// - /// Mints a Composable data object for NFT(s) from a `Collection` - /// with regulated supply - /// Mints a Collectible data object for NFT(s) from an unregulated - /// `Collection`. - /// The only way to mint the NFT data for a collection is to give a - /// reference to [`UID`]. One is only allowed to mint `Nft`s for a - /// given collection if one is the `MintAuthority` owner. - /// - /// This function call bootstraps the minting of leaf node NFTs in a - /// Composable collection with regulated supply. This function does - /// not serve to compose Composable objects, but simply to create the - /// intial objects that are supposed to give rise to the composability tree. - /// - /// For a regulated collection with supply of 100 objects, this function - /// will be called in total 100 times to mint such objects. Once these - /// objects are brought to existance the collection creator can start - /// creating composable objects which determine which NFTs can be merged - /// and what the supply of those configurations are. - /// - /// To be called by the Witness Module deployed by NFT creator. - public fun mint_regulated_nft_data( + // /// Mints loose NFT `Composable` data object and shares it. + // /// Invokes `mint_and_share_data()`. + // /// + // /// Mints a Collectible data object for NFT(s) from an unregulated + // /// `Collection`. + // /// The only way to mint the NFT data for a collection is to give a + // /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + // /// given collection if one is the `MintAuthority` owner. + // /// + // /// This function call bootstraps the minting of leaf node NFTs in a + // /// Composable collection with unregulated supply. This function does not + // /// serve to compose Composable objects, but simply to create the intial + // /// objects that are supposed to give rise to the composability tree. + // /// + // /// To be called by the Witness Module deployed by NFT creator. + // // TODO: delete this function, recylcle the documentation + // public fun mint_unregulated_nft_data( + // name: vector, + // description: vector, + // url: vector, + // attribute_keys: vector>, + // attribute_values: vector>, + // max_supply: u64, + // mint: &MintAuthority, + // ctx: &mut TxContext, + // ) { + // // Assert that it has an uregulated supply policy + // assert!( + // !supply_policy::regulated(collection::supply_policy(mint)), + // err::supply_policy_mismatch(), + // ); + + // let args = mint_args( + // name, + // description, + // url, + // to_string_vector(&mut attribute_keys), + // to_string_vector(&mut attribute_values), + // max_supply, + // ); + + // mint_and_share_data( + // args, + // collection::mint_collection_id(mint), + // max_supply, + // ctx, + // ); + // } + + // TODO: Documentation comments + public fun prepare_direct_mint( name: vector, description: vector, url: vector, @@ -207,12 +259,80 @@ module nft_protocol::c_nft { collection::increment_supply(mint, 1); + let data_uid = object::new(ctx); + mint_and_share_data( + data_uid, args, collection::mint_collection_id(mint), max_supply, + ); + } + + // TODO: Documentation comments + public fun prepare_thunder_mint( + name: vector, + description: vector, + url: vector, + attribute_keys: vector>, + attribute_values: vector>, + max_supply: u64, + mint: &mut MintAuthority, + ctx: &mut TxContext, + ) { + // Assert that it has a regulated supply policy + assert!( + supply_policy::regulated(collection::supply_policy(mint)), + err::supply_policy_mismatch(), + ); + + let args = mint_args( + name, + description, + url, + to_string_vector(&mut attribute_keys), + to_string_vector(&mut attribute_values), + max_supply, + ); + + collection::increment_supply(mint, 1); + + let data_uid = object::new(ctx); + + mint_and_share_data( + data_uid, + args, + collection::mint_collection_id(mint), + max_supply, + ); + } + + /// Mints loose NFT and transfers it to `recipient` + /// Invokes `mint_nft_loose()`. + /// This function call comes after the minting of the leaf node + /// `Collectibles` data object. + /// + /// To be called by Launchpad contract + /// TODO: The flow here needs to be reconsidered + /// TODO: To be deprecated --> calls should be done to the nft module + public fun mint( + nft_data: &mut Composable, + recipient: address, + ctx: &mut TxContext, + ) { + // TODO: should we allow for the minting of more than one NFT at + // a time? + supply::increment_supply(&mut nft_data.supply, 1); + + let nft = nft::mint_nft_loose( + nft_data_id(nft_data), ctx, ); + + transfer::transfer( + nft, + recipient, + ); } /// Function that receives and temporarily holds two or more objects, @@ -220,7 +340,7 @@ module nft_protocol::c_nft { /// holders of those NFTs to merge them together to create a cNFT. /// /// The newly composed object has a its own maximum supply of NFTs. - public entry fun compose_data_objects + public entry fun define_combo_nft ( nfts_data: vector>, @@ -281,34 +401,6 @@ module nft_protocol::c_nft { transfer::share_object(combo_data); } - /// Mints loose NFT and transfers it to `recipient` - /// Invokes `mint_nft_loose()`. - /// This function call comes after the minting of the leaf node - /// `Collectibles` data object. - /// - /// To be called by Launchpad contract - /// TODO: The flow here needs to be reconsidered - /// TODO: To be deprecated --> calls should be done to the nft module - public fun mint_nft( - nft_data: &mut Composable, - recipient: address, - ctx: &mut TxContext, - ) { - // TODO: should we allow for the minting of more than one NFT at - // a time? - supply::increment_supply(&mut nft_data.supply, 1); - - let nft = nft::mint_nft_loose( - nft_data_id(nft_data), - ctx, - ); - - transfer::transfer( - nft, - recipient, - ); - } - // === Entrypoints === /// Mints a cNFT by "merging" two or more NFTs. The function will @@ -321,7 +413,7 @@ module nft_protocol::c_nft { /// further NFTs could be minted and reach the maximum supply. When the /// cNFT would be split back into its constituent components it could result /// in a supply bigger than the maximum supply. - public entry fun mint_c_nft( + public entry fun mint_combo_nft( nfts: vector>>, nfts_data: vector>, // TODO: Ideally we would pass &Data combo_data: &mut Composable, @@ -387,7 +479,7 @@ module nft_protocol::c_nft { /// do not increment the supply of its constituent objects. The reason for /// this is because we do not decrement the supply of these constituent /// objects when we merge them, therefore we maintain consistency. - public entry fun split_c_nft( + public entry fun split_combo_nft( nft: Nft>, c_nft_data: &mut Composable, nfts_data: vector>, @@ -508,13 +600,11 @@ module nft_protocol::c_nft { } fun mint_and_share_data( + data_id: UID, args: MintArgs, collection_id: ID, max_supply: u64, - ctx: &mut TxContext, ) { - let data_id = object::new(ctx); - event::emit( MintDataEvent { object_id: object::uid_to_inner(&data_id), diff --git a/sources/nft/nfts/collectible.move b/sources/nft/nfts/collectible.move index 9496553a..5d5fd7cf 100644 --- a/sources/nft/nfts/collectible.move +++ b/sources/nft/nfts/collectible.move @@ -12,7 +12,9 @@ module nft_protocol::collectible { use sui::url::{Self, Url}; use nft_protocol::err; + use nft_protocol::sale; use nft_protocol::supply_policy; + use nft_protocol::slingshot::{Self, Slingshot}; use nft_protocol::collection::{Self, Collection, MintAuthority}; use nft_protocol::utils::{to_string_vector}; use nft_protocol::supply::{Self, Supply}; @@ -58,26 +60,30 @@ module nft_protocol::collectible { /// Mints loose NFT `Collectible` data and shares it. /// Invokes `mint_and_share_data()`. /// - /// Mints a Collectible data object for NFT(s) from an unregulated + /// Mints a Collectible data object for NFT(s) from a regulated /// `Collection`. /// The only way to mint the NFT data for a collection is to give a /// reference to [`UID`]. One is only allowed to mint `Nft`s for a /// given collection if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - public fun mint_unregulated_nft_data( + /// + /// To be called by the Witness Module deployed by NFT creator. + public fun prepare_launchpad_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, max_supply: u64, - mint: &MintAuthority, + mint: &mut MintAuthority, + sale_outlet: u64, + launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - // Assert that it has an unregulated supply policy + // Assert that it has a regulated supply policy assert!( - !supply_policy::regulated(collection::supply_policy(mint)), + supply_policy::regulated(collection::supply_policy(mint)), err::supply_policy_mismatch(), ); @@ -90,26 +96,72 @@ module nft_protocol::collectible { max_supply, ); + collection::increment_supply(mint, 1); + + let data_uid = object::new(ctx); + let data_id = object::uid_to_inner(&data_uid); + mint_and_share_data( + data_uid, args, collection::mint_collection_id(mint), - ctx, + ); + + let sale = slingshot::sale_mut(launchpad, sale_outlet); + + sale::add_nft( + sale, + data_id, + max_supply ); } - /// Mints loose NFT `Collectible` data and shares it. - /// Invokes `mint_and_share_data()`. - /// - /// Mints a Collectible data object for NFT(s) from a regulated - /// `Collection`. - /// The only way to mint the NFT data for a collection is to give a - /// reference to [`UID`]. One is only allowed to mint `Nft`s for a - /// given collection if one is the `MintAuthority` owner. - /// - /// To be called by the Witness Module deployed by NFT creator. - /// - /// To be called by the Witness Module deployed by NFT creator. - public fun mint_regulated_nft_data( + // // TODO: REMOVE THIS, THERE IS NO POINT BEING REGULATED WHILE USING THE LAUNCHPAD... + // /// Mints loose NFT `Collectible` data and shares it. + // /// Invokes `mint_and_share_data()`. + // /// + // /// Mints a Collectible data object for NFT(s) from an unregulated + // /// `Collection`. + // /// The only way to mint the NFT data for a collection is to give a + // /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + // /// given collection if one is the `MintAuthority` owner. + // /// + // /// To be called by the Witness Module deployed by NFT creator. + // /// TODO: delete this function, recylcle the documentation + // public fun prepare_nft_release_unregulated( + // name: vector, + // description: vector, + // url: vector, + // attribute_keys: vector>, + // attribute_values: vector>, + // max_supply: u64, + // mint: &MintAuthority, + // ctx: &mut TxContext, + // ) { + // // Assert that it has an unregulated supply policy + // assert!( + // !supply_policy::regulated(collection::supply_policy(mint)), + // err::supply_policy_mismatch(), + // ); + + // let args = mint_args( + // name, + // description, + // url, + // to_string_vector(&mut attribute_keys), + // to_string_vector(&mut attribute_values), + // max_supply, + // ); + + // mint_and_share_data( + // args, + // collection::mint_collection_id(mint), + // ctx, + // ); + // } + + // TODO: Documentation comments + public fun prepare_direct_mint( name: vector, description: vector, url: vector, @@ -136,21 +188,59 @@ module nft_protocol::collectible { collection::increment_supply(mint, 1); + let data_uid = object::new(ctx); + mint_and_share_data( + data_uid, args, collection::mint_collection_id(mint), - ctx, ); } + // TODO: Documentation comments + public fun prepare_thunder_mint( + name: vector, + description: vector, + url: vector, + attribute_keys: vector>, + attribute_values: vector>, + max_supply: u64, + mint: &mut MintAuthority, + ctx: &mut TxContext, + ) { + // Assert that it has a regulated supply policy + assert!( + supply_policy::regulated(collection::supply_policy(mint)), + err::supply_policy_mismatch(), + ); + + let args = mint_args( + name, + description, + url, + to_string_vector(&mut attribute_keys), + to_string_vector(&mut attribute_values), + max_supply, + ); + + collection::increment_supply(mint, 1); + + let data_uid = object::new(ctx); + + mint_and_share_data( + data_uid, + args, + collection::mint_collection_id(mint), + ); + } + + // TODO: Update documentation /// Mints loose NFT and transfers it to `recipient` /// Invokes `mint_nft_loose()`. /// This function call comes after the minting of the `Data` object. /// - /// To be called by Launchpad contract /// TODO: The flow here needs to be reconsidered - /// TODO: To be deprecated --> calls should be done to the nft module - public fun mint_nft( + public fun mint( _mint: &MintAuthority, nft_data: &mut Collectible, recipient: address, @@ -383,12 +473,10 @@ module nft_protocol::collectible { } fun mint_and_share_data( + data_id: UID, args: MintArgs, collection_id: ID, - ctx: &mut TxContext, ) { - let data_id = object::new(ctx); - event::emit( MintDataEvent { object_id: object::uid_to_inner(&data_id), diff --git a/sources/nft/nfts/unique_nft.move b/sources/nft/nfts/unique_nft.move index 6d28a2aa..25d60426 100644 --- a/sources/nft/nfts/unique_nft.move +++ b/sources/nft/nfts/unique_nft.move @@ -17,8 +17,6 @@ module nft_protocol::unique_nft { use nft_protocol::collection::{Self, MintAuthority}; use nft_protocol::utils::{to_string_vector}; use nft_protocol::supply_policy; - use nft_protocol::slingshot::{Self, Slingshot}; - use nft_protocol::sale; use nft_protocol::nft::{Self, Nft}; /// An NFT `Unique` data object with standard fields. @@ -55,50 +53,7 @@ module nft_protocol::unique_nft { // === Functions exposed to Witness Module === - /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. - /// Invokes `mint_to_launchpad()`. - /// Mints an NFT from a `Collection` with unregulated supply. - /// The only way to mint the NFT for a collection is to give a reference to - /// [`UID`]. One is only allowed to mint `Nft`s for a given collection - /// if one is the `MintAuthority` owner. - /// - /// To be called by the Witness Module deployed by NFT creator. - public fun mint_unregulated_nft( - name: vector, - description: vector, - url: vector, - attribute_keys: vector>, - attribute_values: vector>, - mint: &MintAuthority, - sale_index: u64, - // TODO: Ideally we do not take a mutable reference such that - // no lock is needed - launchpad: &mut Slingshot, - ctx: &mut TxContext, - ) { - // Assert that it has an unregulated supply policy - assert!( - !supply_policy::regulated(collection::supply_policy(mint)), - err::supply_policy_mismatch(), - ); - - let args = mint_args( - name, - description, - url, - to_string_vector(&mut attribute_keys), - to_string_vector(&mut attribute_values), - ); - - mint_to_launchpad( - args, - collection::mint_collection_id(mint), - sale_index, - launchpad, - ctx, - ); - } - + // TODO: updated comment /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. /// Invokes `mint_to_launchpad()`. /// Mints an NFT from a `Collection` with regulated supply. @@ -107,15 +62,13 @@ module nft_protocol::unique_nft { /// if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - public fun mint_regulated_nft( + public fun prepare_launchpad_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint: &mut MintAuthority, - sale_index: u64, - launchpad: &mut Slingshot, ctx: &mut TxContext, ) { // Assert that it has regulated supply policy @@ -134,37 +87,35 @@ module nft_protocol::unique_nft { collection::increment_supply(mint, 1); - mint_to_launchpad( + mint_and_share_data( args, collection::mint_collection_id(mint), - sale_index, - launchpad, ctx, ); } /// Mint one embedded `Nft` with `Unique` data and send it to `recipient`. /// Invokes `mint_and_transfer()`. - /// Mints an NFT from a `Collection` with unregulated supply. + /// Mints an NFT from a `Collection` with regulated supply. /// The only way to mint the NFT for a collection is to give a reference to /// [`UID`]. One is only allowed to mint `Nft`s for a given collection /// if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - public fun direct_mint_unregulated_nft( + public entry fun mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, - mint: &MintAuthority, + mint: &mut MintAuthority, recipient: address, ctx: &mut TxContext, ) { - // Assert that it has an unregulated supply policy + // Assert that it has a regulated supply policy assert!( - !supply_policy::regulated(collection::supply_policy(mint)), - err::supply_policy_mismatch() + supply_policy::regulated(collection::supply_policy(mint)), + err::supply_policy_mismatch(), ); let args = mint_args( @@ -174,6 +125,7 @@ module nft_protocol::unique_nft { to_string_vector(&mut attribute_keys), to_string_vector(&mut attribute_values), ); + collection::increment_supply(mint, 1); mint_and_transfer( args, @@ -183,28 +135,67 @@ module nft_protocol::unique_nft { ); } + // // TODO: To be removed, recycle the comment + // /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. + // /// Invokes `mint_to_launchpad()`. + // /// Mints an NFT from a `Collection` with unregulated supply. + // /// The only way to mint the NFT for a collection is to give a reference to + // /// [`UID`]. One is only allowed to mint `Nft`s for a given collection + // /// if one is the `MintAuthority` owner. + // /// + // /// To be called by the Witness Module deployed by NFT creator. + // public fun prepare_nft_release_unregulated( + // name: vector, + // description: vector, + // url: vector, + // attribute_keys: vector>, + // attribute_values: vector>, + // mint: &MintAuthority, + // ctx: &mut TxContext, + // ) { + // // Assert that it has an unregulated supply policy + // assert!( + // !supply_policy::regulated(collection::supply_policy(mint)), + // err::supply_policy_mismatch(), + // ); + + // let args = mint_args( + // name, + // description, + // url, + // to_string_vector(&mut attribute_keys), + // to_string_vector(&mut attribute_values), + // ); + + // mint_and_share_data( + // args, + // collection::mint_collection_id(mint), + // ctx, + // ); + // } + /// Mint one embedded `Nft` with `Unique` data and send it to `recipient`. /// Invokes `mint_and_transfer()`. - /// Mints an NFT from a `Collection` with regulated supply. + /// Mints an NFT from a `Collection` with unregulated supply. /// The only way to mint the NFT for a collection is to give a reference to /// [`UID`]. One is only allowed to mint `Nft`s for a given collection /// if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - public fun direct_mint_regulated_nft( + public fun thunder_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, - mint: &mut MintAuthority, + mint: &MintAuthority, recipient: address, ctx: &mut TxContext, ) { - // Assert that it has a regulated supply policy + // Assert that it has an unregulated supply policy assert!( - supply_policy::regulated(collection::supply_policy(mint)), - err::supply_policy_mismatch(), + !supply_policy::regulated(collection::supply_policy(mint)), + err::supply_policy_mismatch() ); let args = mint_args( @@ -214,7 +205,6 @@ module nft_protocol::unique_nft { to_string_vector(&mut attribute_keys), to_string_vector(&mut attribute_values), ); - collection::increment_supply(mint, 1); mint_and_transfer( args, @@ -340,11 +330,10 @@ module nft_protocol::unique_nft { ); } - fun mint_to_launchpad( + // TODO: add documentation + fun mint_and_share_data( args: MintArgs, collection_id: ID, - sale_index: u64, - launchpad: &mut Slingshot, ctx: &mut TxContext, ) { let data_id = object::new(ctx); @@ -356,7 +345,7 @@ module nft_protocol::unique_nft { } ); - let nft_data = Unique { + let data = Unique { id: data_id, name: args.name, description: args.description, @@ -365,20 +354,7 @@ module nft_protocol::unique_nft { attributes: args.attributes, }; - let nft = nft::mint_nft_embedded( - nft_data_id(&nft_data), - nft_data, - ctx - ); - - let sale = slingshot::sale_mut(launchpad, sale_index); - - sale::add_nft(sale, nft::id(&nft)); - - transfer::transfer_to_object( - nft, - launchpad, - ); + transfer::share_object(data); } fun burn_nft_( From 6b362fa51ea431c2f4d2c79f34a1a536062a81d4 Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:15:16 +0000 Subject: [PATCH 26/35] Documentation comments --- sources/nft/nfts/c_nft.move | 141 ++++++++++++++++-------------- sources/nft/nfts/collectible.move | 12 +-- 2 files changed, 84 insertions(+), 69 deletions(-) diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index 2db5316d..9e02903e 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -111,24 +111,29 @@ module nft_protocol::c_nft { // === Functions exposed to Witness Module === - /// Mints loose NFT `Composable` data and shares it. + /// Creates a `Composable` data object, shares it, and adds it's `ID` to + /// a dedicated launchpad `sale_outlet`. Composable NFTs have themselves + /// a supply, and therefore the parameter `max_supply` determines how many + /// NFTs can be minted from the launchpad. + /// /// Invokes `mint_and_share_data()`. /// - /// Mints a Composable data object for NFT(s) from a `Collection` - /// with regulated supply - /// Mints a Collectible data object for NFT(s) from an unregulated - /// `Collection`. + /// Creates a Composable data object for NFT(s) from a `Collection` + /// with regulated supply. Note that unregulated collections should not use + /// the launchpad since the minting process would stop taking advangage of + /// the fast broadcast transactions. + /// /// The only way to mint the NFT data for a collection is to give a /// reference to [`UID`]. One is only allowed to mint `Nft`s for a /// given collection if one is the `MintAuthority` owner. /// /// This function call bootstraps the minting of leaf node NFTs in a /// Composable collection with regulated supply. This function does - /// not serve to compose Composable objects, but simply to create the + /// not serve to compose Combo objects, but simply to create the /// intial objects that are supposed to give rise to the composability tree. /// /// For a regulated collection with supply of 100 objects, this function - /// will be called in total 100 times to mint such objects. Once these + /// ought to be called 100 times in total to mint such objects. Once these /// objects are brought to existance the collection creator can start /// creating composable objects which determine which NFTs can be merged /// and what the supply of those configurations are. @@ -182,56 +187,33 @@ module nft_protocol::c_nft { ); } - // /// Mints loose NFT `Composable` data object and shares it. - // /// Invokes `mint_and_share_data()`. - // /// - // /// Mints a Collectible data object for NFT(s) from an unregulated - // /// `Collection`. - // /// The only way to mint the NFT data for a collection is to give a - // /// reference to [`UID`]. One is only allowed to mint `Nft`s for a - // /// given collection if one is the `MintAuthority` owner. - // /// - // /// This function call bootstraps the minting of leaf node NFTs in a - // /// Composable collection with unregulated supply. This function does not - // /// serve to compose Composable objects, but simply to create the intial - // /// objects that are supposed to give rise to the composability tree. - // /// - // /// To be called by the Witness Module deployed by NFT creator. - // // TODO: delete this function, recylcle the documentation - // public fun mint_unregulated_nft_data( - // name: vector, - // description: vector, - // url: vector, - // attribute_keys: vector>, - // attribute_values: vector>, - // max_supply: u64, - // mint: &MintAuthority, - // ctx: &mut TxContext, - // ) { - // // Assert that it has an uregulated supply policy - // assert!( - // !supply_policy::regulated(collection::supply_policy(mint)), - // err::supply_policy_mismatch(), - // ); - - // let args = mint_args( - // name, - // description, - // url, - // to_string_vector(&mut attribute_keys), - // to_string_vector(&mut attribute_values), - // max_supply, - // ); - - // mint_and_share_data( - // args, - // collection::mint_collection_id(mint), - // max_supply, - // ctx, - // ); - // } - - // TODO: Documentation comments + /// Creates `Composable` data, shares it, with the intent of preparing for + /// a direct mint. Composable NFTs have themselves a supply, and therefore + /// the parameter `max_supply` determines how many NFTs can be minted. + /// + /// Invokes `mint_and_share_data()`. + /// + /// Creates a Composable data object for NFT(s) from a `Collection` + /// with regulated supply. Note that unregulated collections should use the + /// thunder mint instead, in order to take advantage of fast broadcast + /// transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// This function call bootstraps the minting of leaf node NFTs in a + /// Composable collection with regulated supply. This function does + /// not serve to compose Combo objects, but simply to create the + /// intial objects that are supposed to give rise to the composability tree. + /// + /// For a regulated collection with supply of 100 objects, this function + /// ought to be called 100 times in total to mint such objects. Once these + /// objects are brought to existance the collection creator can start + /// creating composable objects which determine which NFTs can be merged + /// and what the supply of those configurations are. + /// + /// To be called by the Witness Module deployed by NFT creator. public fun prepare_direct_mint( name: vector, description: vector, @@ -269,7 +251,37 @@ module nft_protocol::c_nft { ); } - // TODO: Documentation comments + /// Creates `Composable` data, shares it, with the intent of preparing for + /// a thunder mint. A thunder mint works like a direct mint, except that it + /// takes full advantage of fast broadcast transactions. Composable NFTs + /// have themselves a supply, and therefore the parameter `max_supply` + /// determines how many NFTs can be minted. + /// + /// Invokes `mint_and_share_data()`. + /// + /// Creates a Composable data object for NFT(s) from a `Collection` + /// with unregulated supply. Note that regulated collections should use the + /// direct mint instead, since they won't be able to tap into fast + /// broadcast transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// This function call bootstraps the minting of leaf node NFTs in a + /// Composable collection with regulated supply. This function does + /// not serve to compose Combo objects, but simply to create the + /// intial objects that are supposed to give rise to the composability tree. + /// + /// For a unregulates collections with inderterminate supply, this function + /// ought to be called as many times as the owner of the `MintAuthority` + /// wants, corresponding to the amount of data objects the Creator wants to + /// have for the collection. Once these objects are brought to existance + /// the collection creator can start creating composable objects which + /// determine which NFTs can be merged and what the supply of those + /// configurations are. + /// + /// To be called by the Witness Module deployed by NFT creator. public fun prepare_thunder_mint( name: vector, description: vector, @@ -307,15 +319,16 @@ module nft_protocol::c_nft { ); } - /// Mints loose NFT and transfers it to `recipient` + /// Mints loose NFT and transfers it to `recipient`. This is an entry + /// function to be called by the client code for direct or thunder mints. + /// For launchpad mints, the launchpad calls `nft::mint_nft_loose()` + /// directly. + /// /// Invokes `mint_nft_loose()`. + /// /// This function call comes after the minting of the leaf node /// `Collectibles` data object. - /// - /// To be called by Launchpad contract - /// TODO: The flow here needs to be reconsidered - /// TODO: To be deprecated --> calls should be done to the nft module - public fun mint( + public entry fun mint( nft_data: &mut Composable, recipient: address, ctx: &mut TxContext, diff --git a/sources/nft/nfts/collectible.move b/sources/nft/nfts/collectible.move index 5d5fd7cf..05ef4a61 100644 --- a/sources/nft/nfts/collectible.move +++ b/sources/nft/nfts/collectible.move @@ -57,18 +57,20 @@ module nft_protocol::collectible { // === Functions exposed to Witness Module === - /// Mints loose NFT `Collectible` data and shares it. + /// Mints loose NFT `Collectible` data and shares it, and adds it's `ID` to + /// a dedicated launchpad `sale_outlet`. Collectible NFTs have themselves + /// a supply, and therefore the parameter `max_supply` determines how many + /// NFTs can be minted from the launchpad. + /// /// Invokes `mint_and_share_data()`. /// - /// Mints a Collectible data object for NFT(s) from a regulated - /// `Collection`. + /// Creates a Collectible data object for NFT(s) from a `Collection` + /// with regulated supply. /// The only way to mint the NFT data for a collection is to give a /// reference to [`UID`]. One is only allowed to mint `Nft`s for a /// given collection if one is the `MintAuthority` owner. /// /// To be called by the Witness Module deployed by NFT creator. - /// - /// To be called by the Witness Module deployed by NFT creator. public fun prepare_launchpad_mint( name: vector, description: vector, From d4b229bcd19522a5dfc787e6731b035f5860e663 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 2 Nov 2022 20:49:40 +0100 Subject: [PATCH 27/35] Moving transfer whitelist to trading directory --- sources/{safe => trading}/transfer_whitelist.move | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sources/{safe => trading}/transfer_whitelist.move (100%) diff --git a/sources/safe/transfer_whitelist.move b/sources/trading/transfer_whitelist.move similarity index 100% rename from sources/safe/transfer_whitelist.move rename to sources/trading/transfer_whitelist.move From e0ed26b3add8eb1b2177c683f59d5cbe9a9bfdd5 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 2 Nov 2022 22:31:16 +0100 Subject: [PATCH 28/35] Removing NFT ID as input because it's already included in the transfer cap --- sources/safe/safe.move | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index 673b6908..ca51f4ba 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -226,14 +226,13 @@ module nft_protocol::safe { /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. public fun transfer_nft_to_recipient( - nft_id: ID, transfer_cap: TransferCap, recipient: address, authority: Auth, whitelist: &Whitelist, safe: &mut Safe, ) { - let nft = get_nft_for_transfer_(nft_id, transfer_cap, safe); + let nft = get_nft_for_transfer_(transfer_cap, safe); nft::transfer(nft, recipient, authority, whitelist); } @@ -246,7 +245,6 @@ module nft_protocol::safe { /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. public fun transfer_nft_to_safe( - nft_id: ID, transfer_cap: TransferCap, recipient: address, authority: Auth, @@ -255,7 +253,7 @@ module nft_protocol::safe { target: &mut Safe, ctx: &mut TxContext, ) { - let nft = get_nft_for_transfer_(nft_id, transfer_cap, source); + let nft = get_nft_for_transfer_(transfer_cap, source); nft::change_logical_owner(&mut nft, recipient, authority, whitelist); deposit_nft(nft, target, ctx); @@ -356,10 +354,11 @@ module nft_protocol::safe { } fun get_nft_for_transfer_( - nft_id: ID, transfer_cap: TransferCap, safe: &mut Safe, ): Nft { + let nft_id = transfer_cap.nft; + event::emit( TransferEvent { safe: object::id(safe), From 50fa5322b2eeac0f78fba48bb54dd13a11c4c47f Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Fri, 4 Nov 2022 12:39:28 +0000 Subject: [PATCH 29/35] doc comments --- sources/nft/nfts/c_nft.move | 8 +-- sources/nft/nfts/collectible.move | 108 +++++++++++++++-------------- sources/nft/nfts/unique_nft.move | 111 ++++++++++++++---------------- 3 files changed, 110 insertions(+), 117 deletions(-) diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index 9e02903e..1d372875 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -319,7 +319,7 @@ module nft_protocol::c_nft { ); } - /// Mints loose NFT and transfers it to `recipient`. This is an entry + /// Mints Composable NFT and transfers it to `recipient`. This is an entry /// function to be called by the client code for direct or thunder mints. /// For launchpad mints, the launchpad calls `nft::mint_nft_loose()` /// directly. @@ -327,7 +327,7 @@ module nft_protocol::c_nft { /// Invokes `mint_nft_loose()`. /// /// This function call comes after the minting of the leaf node - /// `Collectibles` data object. + /// `Composable` data object. public entry fun mint( nft_data: &mut Composable, recipient: address, @@ -583,7 +583,7 @@ module nft_protocol::c_nft { &data.attributes } - /// Get the Nft Collectible's `attributes.keys` + /// Get the Nft Composable's `attributes.keys` public fun attribute_keys( comp: &Composable, ): &vector { @@ -591,7 +591,7 @@ module nft_protocol::c_nft { &data.attributes.keys } - /// Get the Nft Collectible's `attributes.values` + /// Get the Nft Composable's `attributes.values` public fun attribute_values( comp: &Composable, ): &vector { diff --git a/sources/nft/nfts/collectible.move b/sources/nft/nfts/collectible.move index 05ef4a61..04dbfaf7 100644 --- a/sources/nft/nfts/collectible.move +++ b/sources/nft/nfts/collectible.move @@ -57,7 +57,7 @@ module nft_protocol::collectible { // === Functions exposed to Witness Module === - /// Mints loose NFT `Collectible` data and shares it, and adds it's `ID` to + /// Creates a `Collectible` data object, shares it, and adds it's `ID` to /// a dedicated launchpad `sale_outlet`. Collectible NFTs have themselves /// a supply, and therefore the parameter `max_supply` determines how many /// NFTs can be minted from the launchpad. @@ -65,11 +65,17 @@ module nft_protocol::collectible { /// Invokes `mint_and_share_data()`. /// /// Creates a Collectible data object for NFT(s) from a `Collection` - /// with regulated supply. + /// with regulated supply. Note that unregulated collections should not use + /// the launchpad since the minting process would stop taking advangage of + /// the fast broadcast transactions. + /// /// The only way to mint the NFT data for a collection is to give a /// reference to [`UID`]. One is only allowed to mint `Nft`s for a /// given collection if one is the `MintAuthority` owner. /// + /// For a regulated collection with supply of 100 objects, this function + /// ought to be called 100 times in total to mint such objects. + /// /// To be called by the Witness Module deployed by NFT creator. public fun prepare_launchpad_mint( name: vector, @@ -118,51 +124,25 @@ module nft_protocol::collectible { ); } - // // TODO: REMOVE THIS, THERE IS NO POINT BEING REGULATED WHILE USING THE LAUNCHPAD... - // /// Mints loose NFT `Collectible` data and shares it. - // /// Invokes `mint_and_share_data()`. - // /// - // /// Mints a Collectible data object for NFT(s) from an unregulated - // /// `Collection`. - // /// The only way to mint the NFT data for a collection is to give a - // /// reference to [`UID`]. One is only allowed to mint `Nft`s for a - // /// given collection if one is the `MintAuthority` owner. - // /// - // /// To be called by the Witness Module deployed by NFT creator. - // /// TODO: delete this function, recylcle the documentation - // public fun prepare_nft_release_unregulated( - // name: vector, - // description: vector, - // url: vector, - // attribute_keys: vector>, - // attribute_values: vector>, - // max_supply: u64, - // mint: &MintAuthority, - // ctx: &mut TxContext, - // ) { - // // Assert that it has an unregulated supply policy - // assert!( - // !supply_policy::regulated(collection::supply_policy(mint)), - // err::supply_policy_mismatch(), - // ); - - // let args = mint_args( - // name, - // description, - // url, - // to_string_vector(&mut attribute_keys), - // to_string_vector(&mut attribute_values), - // max_supply, - // ); - - // mint_and_share_data( - // args, - // collection::mint_collection_id(mint), - // ctx, - // ); - // } - - // TODO: Documentation comments + /// Creates `Collectible` data, shares it, with the intent of preparing for + /// a direct mint. Collectible NFTs have themselves a supply, and therefore + /// the parameter `max_supply` determines how many NFTs can be minted. + /// + /// Invokes `mint_and_share_data()`. + /// + /// Creates a Collectible data object for NFT(s) from a `Collection` + /// with regulated supply. Note that unregulated collections should use the + /// thunder mint instead, in order to take advantage of fast broadcast + /// transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// For a regulated collection with supply of 100 objects, this function + /// ought to be called 100 times in total to mint such objects. + /// + /// To be called by the Witness Module deployed by NFT creator. public fun prepare_direct_mint( name: vector, description: vector, @@ -199,7 +179,29 @@ module nft_protocol::collectible { ); } - // TODO: Documentation comments + /// Creates `Collectible` data, shares it, with the intent of preparing for + /// a thunder mint. A thunder mint works like a direct mint, except that it + /// takes full advantage of fast broadcast transactions. Collectible NFTs + /// have themselves a supply, and therefore the parameter `max_supply` + /// determines how many NFTs can be minted. + /// + /// Invokes `mint_and_share_data()`. + /// + /// Creates a Collectible data object for NFT(s) from a `Collection` + /// with unregulated supply. Note that regulated collections should use the + /// direct mint instead, since they won't be able to tap into fast + /// broadcast transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// For a unregulates collections with inderterminate supply, this function + /// ought to be called as many times as the owner of the `MintAuthority` + /// wants, corresponding to the amount of data objects the Creator wants to + /// have for the collection. + /// + /// To be called by the Witness Module deployed by NFT creator. public fun prepare_thunder_mint( name: vector, description: vector, @@ -236,12 +238,12 @@ module nft_protocol::collectible { ); } - // TODO: Update documentation - /// Mints loose NFT and transfers it to `recipient` - /// Invokes `mint_nft_loose()`. - /// This function call comes after the minting of the `Data` object. + /// Mints Collectible NFT and transfers it to `recipient`. This is an entry + /// function to be called by the client code for direct or thunder mints. + /// For launchpad mints, the launchpad calls `nft::mint_nft_loose()` + /// directly. /// - /// TODO: The flow here needs to be reconsidered + /// Invokes `mint_nft_loose()`. public fun mint( _mint: &MintAuthority, nft_data: &mut Collectible, diff --git a/sources/nft/nfts/unique_nft.move b/sources/nft/nfts/unique_nft.move index 25d60426..ba595627 100644 --- a/sources/nft/nfts/unique_nft.move +++ b/sources/nft/nfts/unique_nft.move @@ -3,6 +3,7 @@ //! It acts as a standard domain-specific implementation of an NFT type, //! fitting use cases such as Art and PFP NFT Collections. It uses the main //! NFT module to mint embedded NFTs. +//! TODO: Rename this Type to `Classic` module nft_protocol::unique_nft { use sui::event; use sui::object::{Self, UID, ID}; @@ -53,13 +54,22 @@ module nft_protocol::unique_nft { // === Functions exposed to Witness Module === - // TODO: updated comment - /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. - /// Invokes `mint_to_launchpad()`. - /// Mints an NFT from a `Collection` with regulated supply. - /// The only way to mint the NFT for a collection is to give a reference to - /// [`UID`]. One is only allowed to mint `Nft`s for a given collection - /// if one is the `MintAuthority` owner. + /// Creates a `Unique` data object, shares it, and adds it's `ID` to + /// a dedicated launchpad `sale_outlet`. + /// + /// Invokes `mint_and_share_data()`. + /// + /// Creates a Unique data object for NFT(s) from a `Collection` + /// with regulated supply. Note that unregulated collections should not use + /// the launchpad since the minting process would stop taking advangage of + /// the fast broadcast transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// For a regulated collection with supply of 100 objects, this function + /// ought to be called 100 times in total to mint such objects. /// /// To be called by the Witness Module deployed by NFT creator. public fun prepare_launchpad_mint( @@ -94,15 +104,24 @@ module nft_protocol::unique_nft { ); } - /// Mint one embedded `Nft` with `Unique` data and send it to `recipient`. + /// Mints Unique NFT and transfers it to `recipient`. This is an entry + /// function to be called by the client code for direct mints. + /// For launchpad mints, the launchpad calls `nft::mint_nft_loose()` + /// directly and then `nft::join_nft_data()` to make it an embedded nft. + /// /// Invokes `mint_and_transfer()`. - /// Mints an NFT from a `Collection` with regulated supply. - /// The only way to mint the NFT for a collection is to give a reference to - /// [`UID`]. One is only allowed to mint `Nft`s for a given collection - /// if one is the `MintAuthority` owner. /// - /// To be called by the Witness Module deployed by NFT creator. - public entry fun mint( + /// Mints a Unique NFT from a `Collection` with regulated supply. + /// Note that unregulated collections should use the thunder mint instead, + /// in order to take advantage of fast broadcast transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// For a regulated collection with supply of 100 objects, this function + /// ought to be called 100 times in total to mint such objects. + public entry fun direct_mint( name: vector, description: vector, url: vector, @@ -135,54 +154,26 @@ module nft_protocol::unique_nft { ); } - // // TODO: To be removed, recycle the comment - // /// Mint one embedded `Nft` with `Unique` data and send it to `Launchpad`. - // /// Invokes `mint_to_launchpad()`. - // /// Mints an NFT from a `Collection` with unregulated supply. - // /// The only way to mint the NFT for a collection is to give a reference to - // /// [`UID`]. One is only allowed to mint `Nft`s for a given collection - // /// if one is the `MintAuthority` owner. - // /// - // /// To be called by the Witness Module deployed by NFT creator. - // public fun prepare_nft_release_unregulated( - // name: vector, - // description: vector, - // url: vector, - // attribute_keys: vector>, - // attribute_values: vector>, - // mint: &MintAuthority, - // ctx: &mut TxContext, - // ) { - // // Assert that it has an unregulated supply policy - // assert!( - // !supply_policy::regulated(collection::supply_policy(mint)), - // err::supply_policy_mismatch(), - // ); - - // let args = mint_args( - // name, - // description, - // url, - // to_string_vector(&mut attribute_keys), - // to_string_vector(&mut attribute_values), - // ); - - // mint_and_share_data( - // args, - // collection::mint_collection_id(mint), - // ctx, - // ); - // } - - /// Mint one embedded `Nft` with `Unique` data and send it to `recipient`. + /// Mints Unique NFT and transfers it to `recipient`. This is an entry + /// function to be called by the client code for direct mints. + /// For launchpad mints, the launchpad calls `nft::mint_nft_loose()` + /// directly and then `nft::join_nft_data()` to make it an embedded nft. + /// /// Invokes `mint_and_transfer()`. - /// Mints an NFT from a `Collection` with unregulated supply. - /// The only way to mint the NFT for a collection is to give a reference to - /// [`UID`]. One is only allowed to mint `Nft`s for a given collection - /// if one is the `MintAuthority` owner. /// - /// To be called by the Witness Module deployed by NFT creator. - public fun thunder_mint( + /// Mints a Unique NFT from a `Collection` with unregulated supply. + /// Note that regulated collections should use the direct mint instead, + /// since they won't be able to tap into fast broadcast transactions. + /// + /// The only way to mint the NFT data for a collection is to give a + /// reference to [`UID`]. One is only allowed to mint `Nft`s for a + /// given collection if one is the `MintAuthority` owner. + /// + /// For a unregulates collections with inderterminate supply, this function + /// ought to be called as many times as the owner of the `MintAuthority` + /// wants, corresponding to the amount of data objects the Creator wants to + /// have for the collection. + public entry fun thunder_mint( name: vector, description: vector, url: vector, From b1e9b95504c06af8a3412ffa387d4938d19d4ebd Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Fri, 4 Nov 2022 15:10:03 +0000 Subject: [PATCH 30/35] Adapt gutenberg --- gutenberg/examples/c_nft.yaml | 19 ++++++++ gutenberg/examples/multi_sales.yaml | 2 +- gutenberg/examples/simple.yaml | 9 ++-- gutenberg/src/schema.rs | 18 ++----- gutenberg/src/types.rs | 41 ++++++++++------ gutenberg/templates/template.move | 23 ++++----- gutenberg/tests/generate.rs | 18 +++++++ sources/examples/collectibles.move | 31 +++++++----- sources/examples/multi_sales.move | 31 ++++++------ sources/examples/suimarines.move | 35 ++++++++----- sources/examples/suricannes.move | 76 +++++++++++++++++++++++++++++ sources/launchpad/slingshot.move | 6 +-- sources/nft/nfts/c_nft.move | 2 +- sources/nft/nfts/unique_nft.move | 23 +++++++-- 14 files changed, 240 insertions(+), 94 deletions(-) create mode 100644 gutenberg/examples/c_nft.yaml create mode 100644 sources/examples/suricannes.move diff --git a/gutenberg/examples/c_nft.yaml b/gutenberg/examples/c_nft.yaml new file mode 100644 index 00000000..02c2085d --- /dev/null +++ b/gutenberg/examples/c_nft.yaml @@ -0,0 +1,19 @@ +NftType: "CNft" + +Collection: + name: "Suricannes" + description: "A Unique NFT collection of Hurricanes on Sui" + symbol: "SURI" + max_supply: 100 + receiver: "0x6c86ac4a796204ea09a87b6130db0c38263c1890" + tags: + - "Art" + - "PFP" + royalty_fee_bps: 100 + is_mutable: true + data: "Some extra data" + +Launchpad: + market_type: "fixed_price" + prices: [1000] + whitelists: [false] diff --git a/gutenberg/examples/multi_sales.yaml b/gutenberg/examples/multi_sales.yaml index f8efed36..a48bfbe8 100644 --- a/gutenberg/examples/multi_sales.yaml +++ b/gutenberg/examples/multi_sales.yaml @@ -16,4 +16,4 @@ Collection: Launchpad: market_type: "fixed_price" prices: [1000, 2000, 3000, 4000, 5000] - whitelists: [false, true, true, true] + whitelists: [false, true, true, true, true] diff --git a/gutenberg/examples/simple.yaml b/gutenberg/examples/simple.yaml index 43bf507d..4112c04d 100644 --- a/gutenberg/examples/simple.yaml +++ b/gutenberg/examples/simple.yaml @@ -1,16 +1,15 @@ NftType: "Unique" Collection: - name: "Suisurfers" - description: "A Unique NFT collection of Suisurfers on Sui" - symbol: "SUIS" + name: "Suimarines" + description: "A Unique NFT collection of Submarines on Sui" + symbol: "SUIM" max_supply: 100 receiver: "0x6c86ac4a796204ea09a87b6130db0c38263c1890" tags: - "Art" - - "PFP" royalty_fee_bps: 100 - is_mutable: true + is_mutable: false data: "Some extra data" Launchpad: diff --git a/gutenberg/src/schema.rs b/gutenberg/src/schema.rs index 2f397d57..0b3f608e 100644 --- a/gutenberg/src/schema.rs +++ b/gutenberg/src/schema.rs @@ -99,20 +99,12 @@ impl Schema { let define_whitelists = Schema::write_whitelists(&mut whitelists)?; let define_prices = Schema::write_prices(&mut prices)?; - let market_module_imports = if is_embedded { - format!("::{{Self, {}}}", market_type) - } else { - "".to_string() - } - .into_boxed_str(); + let market_module_imports = + format!("::{{Self, {}}}", market_type).into_boxed_str(); - let slingshot_import = if is_embedded { - "use nft_protocol::slingshot::Slingshot;" - } else { - "" - } - .to_string() - .into_boxed_str(); + let slingshot_import = "use nft_protocol::slingshot::Slingshot;" + .to_string() + .into_boxed_str(); let mint_func = self .nft_type diff --git a/gutenberg/src/types.rs b/gutenberg/src/types.rs index 9e7f31a1..badc2e1b 100644 --- a/gutenberg/src/types.rs +++ b/gutenberg/src/types.rs @@ -56,43 +56,46 @@ impl NftType { // TODO: Need to add support for unregulated collections let func = match self { NftType::Unique => format!( - "public entry fun mint_nft(\n \ + "public entry fun prepare_mint(\n \ name: vector,\n \ description: vector,\n \ url: vector,\n \ attribute_keys: vector>,\n \ attribute_values: vector>,\n \ - mint_authority: &mut MintAuthority<{}>,\n \ - sale_index: u64,\n \ - launchpad: &mut Slingshot<{}, {}>,\n \ + mint_authority: &mut MintAuthority<{witness}>,\n \ + sale_outlet: u64,\n \ + launchpad: &mut Slingshot<{witness}, {market_type}>,\n \ ctx: &mut TxContext,\n \ ) {{\n \ - unique_nft::mint_regulated_nft(\n \ + unique_nft::prepare_launchpad_mint<{witness}, {market_type}>(\n \ name,\n \ description,\n \ url,\n \ attribute_keys,\n \ attribute_values,\n \ mint_authority,\n \ - sale_index,\n \ + sale_outlet,\n \ launchpad,\n \ ctx,\n \ );\n \ }}", - witness, witness, market_type + witness = witness, + market_type = market_type, ), NftType::Collectible => format!( - "public entry fun mint_nft(\n \ + "public entry fun prepare_mint(\n \ name: vector,\n \ description: vector,\n \ url: vector,\n \ attribute_keys: vector>,\n \ attribute_values: vector>,\n \ max_supply: u64,\n \ - mint: &mut MintAuthority<{}>,\n \ + mint: &mut MintAuthority<{witness}>,\n \ + sale_outlet: u64,\n \ + launchpad: &mut Slingshot<{witness}, {market_type}>,\n \ ctx: &mut TxContext,\n \ ) {{\n \ - collectible::mint_regulated_nft_data(\n \ + collectible::prepare_launchpad_mint<{witness}, {market_type}>(\n \ name,\n \ description,\n \ url,\n \ @@ -100,23 +103,28 @@ impl NftType { attribute_values,\n \ max_supply,\n \ mint,\n \ + sale_outlet,\n \ + launchpad,\n \ ctx,\n \ );\n \ }}", - witness, + witness = witness, + market_type = market_type, ), NftType::CNft => format!( - "public entry fun mint_nft(\n \ + "public entry fun prepare_mint(\n \ name: vector,\n \ description: vector,\n \ url: vector,\n \ attribute_keys: vector>,\n \ attribute_values: vector>,\n \ max_supply: u64,\n \ - mint: &mut MintAuthority<{}>,\n \ + mint: &mut MintAuthority<{witness}>,\n \ + sale_outlet: u64,\n \ + launchpad: &mut Slingshot<{witness}, {market_type}>,\n \ ctx: &mut TxContext,\n \ ) {{\n \ - c_nft::mint_regulated_nft_data<{}, c_nft::Data>(\n \ + c_nft::prepare_launchpad_mint<{witness}, {market_type}, c_nft::Data>(\n \ name,\n \ description,\n \ url,\n \ @@ -124,10 +132,13 @@ impl NftType { attribute_values,\n \ max_supply,\n \ mint,\n \ + sale_outlet,\n \ + launchpad,\n \ ctx,\n \ );\n \ }}", - witness, witness + witness = witness, + market_type = market_type, ), }; func.into_boxed_str() diff --git a/gutenberg/templates/template.move b/gutenberg/templates/template.move index 190bf527..c0a4bbcd 100644 --- a/gutenberg/templates/template.move +++ b/gutenberg/templates/template.move @@ -2,18 +2,20 @@ module nft_protocol::{module_name} {{ use std::vector; use sui::tx_context::{{Self, TxContext}}; - - use nft_protocol::collection::{{MintAuthority}}; - use nft_protocol::{market_module}{market_module_imports}; - use nft_protocol::std_collection; + + // NFT Modules use nft_protocol::{nft_type}; + use nft_protocol::std_collection; + use nft_protocol::collection::{{MintAuthority}}; + + // Market Modules {slingshot_import} + use nft_protocol::{market_module}{market_module_imports}; struct {witness} has drop {{}} fun init(witness: {witness}, ctx: &mut TxContext) {{ - let tags: vector> = vector::empty(); - {tags} + let tags: vector> = vector::empty();{tags} let collection_id = std_collection::mint<{witness}>( b"{name}", @@ -21,7 +23,7 @@ module nft_protocol::{module_name} {{ b"{symbol}", // symbol {max_supply}, // max_supply @{receiver}, // Royalty receiver - tags, // tags + tags, {royalty_fee_bps}, // royalty_fee_bps {is_mutable}, // is_mutable b"{extra_data}", @@ -30,17 +32,16 @@ module nft_protocol::{module_name} {{ ); {define_whitelists} - {define_prices} - + {market_module}::{sale_type}( witness, tx_context::sender(ctx), // admin collection_id, @{receiver}, {is_embedded}, // is_embedded - whitelisting, // whitelist - pricing, // price + whitelisting, + pricing, ctx, ); }} diff --git a/gutenberg/tests/generate.rs b/gutenberg/tests/generate.rs index a884b871..f2c82f7d 100644 --- a/gutenberg/tests/generate.rs +++ b/gutenberg/tests/generate.rs @@ -34,6 +34,24 @@ fn collectible() { assert_equal(config, expected); } +#[test] +fn c_nft() { + let config = File::open("./examples/c_nft.yaml").unwrap(); + let expected = + fs::read_to_string("../sources/examples/suricannes.move").unwrap(); + + assert_equal(config, expected); +} + +#[test] +fn simple() { + let config = File::open("./examples/simple.yaml").unwrap(); + let expected = + fs::read_to_string("../sources/examples/suimarines.move").unwrap(); + + assert_equal(config, expected); +} + /// Asserts that the config file has correct schema fn assert_schema(config: File) -> Schema { serde_yaml::from_reader::<_, Schema>(config).unwrap() diff --git a/sources/examples/collectibles.move b/sources/examples/collectibles.move index 9e59d520..51564aa7 100644 --- a/sources/examples/collectibles.move +++ b/sources/examples/collectibles.move @@ -2,18 +2,20 @@ module nft_protocol::suinamis { use std::vector; use sui::tx_context::{Self, TxContext}; - - use nft_protocol::collection::{MintAuthority}; - use nft_protocol::fixed_price; - use nft_protocol::std_collection; + + // NFT Modules use nft_protocol::collectible; - + use nft_protocol::std_collection; + use nft_protocol::collection::{MintAuthority}; + + // Market Modules + use nft_protocol::slingshot::Slingshot; + use nft_protocol::fixed_price::{Self, FixedPriceMarket}; struct SUINAMIS has drop {} fun init(witness: SUINAMIS, ctx: &mut TxContext) { let tags: vector> = vector::empty(); - vector::push_back(&mut tags, b"Art"); vector::push_back(&mut tags, b"PFP"); @@ -23,7 +25,7 @@ module nft_protocol::suinamis { b"SUIN", // symbol 100, // max_supply @0x6c86ac4a796204ea09a87b6130db0c38263c1890, // Royalty receiver - tags, // tags + tags, 100, // royalty_fee_bps true, // is_mutable b"Some extra data", @@ -32,22 +34,21 @@ module nft_protocol::suinamis { ); let whitelisting = false; - let pricing = 1000; - + fixed_price::create_single_market( witness, tx_context::sender(ctx), // admin collection_id, @0x6c86ac4a796204ea09a87b6130db0c38263c1890, false, // is_embedded - whitelisting, // whitelist - pricing, // price + whitelisting, + pricing, ctx, ); } - public entry fun mint_nft( + public entry fun prepare_mint( name: vector, description: vector, url: vector, @@ -55,9 +56,11 @@ module nft_protocol::suinamis { attribute_values: vector>, max_supply: u64, mint: &mut MintAuthority, + sale_outlet: u64, + launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - collectible::mint_regulated_nft_data( + collectible::prepare_launchpad_mint( name, description, url, @@ -65,6 +68,8 @@ module nft_protocol::suinamis { attribute_values, max_supply, mint, + sale_outlet, + launchpad, ctx, ); } diff --git a/sources/examples/multi_sales.move b/sources/examples/multi_sales.move index 22132c0c..65e4f421 100644 --- a/sources/examples/multi_sales.move +++ b/sources/examples/multi_sales.move @@ -2,18 +2,20 @@ module nft_protocol::suimonsters { use std::vector; use sui::tx_context::{Self, TxContext}; - - use nft_protocol::collection::{MintAuthority}; - use nft_protocol::fixed_price::{Self, FixedPriceMarket}; - use nft_protocol::std_collection; + + // NFT Modules use nft_protocol::unique_nft; + use nft_protocol::std_collection; + use nft_protocol::collection::{MintAuthority}; + + // Market Modules use nft_protocol::slingshot::Slingshot; + use nft_protocol::fixed_price::{Self, FixedPriceMarket}; struct SUIMONSTERS has drop {} fun init(witness: SUIMONSTERS, ctx: &mut TxContext) { let tags: vector> = vector::empty(); - vector::push_back(&mut tags, b"Art"); vector::push_back(&mut tags, b"PFP"); @@ -23,7 +25,7 @@ module nft_protocol::suimonsters { b"SUIMO", // symbol 100, // max_supply @0x6c86ac4a796204ea09a87b6130db0c38263c1890, // Royalty receiver - tags, // tags + tags, 100, // royalty_fee_bps true, // is_mutable b"Some extra data", @@ -36,46 +38,47 @@ module nft_protocol::suimonsters { vector::push_back(&mut whitelisting, true); vector::push_back(&mut whitelisting, true); vector::push_back(&mut whitelisting, true); - + vector::push_back(&mut whitelisting, true); let pricing = vector::empty(); vector::push_back(&mut pricing, 1000); vector::push_back(&mut pricing, 2000); vector::push_back(&mut pricing, 3000); vector::push_back(&mut pricing, 4000); + vector::push_back(&mut pricing, 5000); + - fixed_price::create_multi_market( witness, tx_context::sender(ctx), // admin collection_id, @0x6c86ac4a796204ea09a87b6130db0c38263c1890, true, // is_embedded - whitelisting, // whitelist - pricing, // price + whitelisting, + pricing, ctx, ); } - public entry fun mint_nft( + public entry fun prepare_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint_authority: &mut MintAuthority, - sale_index: u64, + sale_outlet: u64, launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - unique_nft::mint_regulated_nft( + unique_nft::prepare_launchpad_mint( name, description, url, attribute_keys, attribute_values, mint_authority, - sale_index, + sale_outlet, launchpad, ctx, ); diff --git a/sources/examples/suimarines.move b/sources/examples/suimarines.move index 8410a685..fa77d768 100644 --- a/sources/examples/suimarines.move +++ b/sources/examples/suimarines.move @@ -1,18 +1,20 @@ module nft_protocol::suimarines { - use sui::tx_context::{Self, TxContext}; - use std::vector; - use nft_protocol::collection::{MintAuthority}; - use nft_protocol::fixed_price; - use nft_protocol::std_collection; + use sui::tx_context::{Self, TxContext}; + + // NFT Modules use nft_protocol::unique_nft; + use nft_protocol::std_collection; + use nft_protocol::collection::{MintAuthority}; + + // Market Modules + use nft_protocol::slingshot::Slingshot; + use nft_protocol::fixed_price::{Self, FixedPriceMarket}; struct SUIMARINES has drop {} fun init(witness: SUIMARINES, ctx: &mut TxContext) { - let receiver = @0xA; - let tags: vector> = vector::empty(); vector::push_back(&mut tags, b"Art"); @@ -21,7 +23,7 @@ module nft_protocol::suimarines { b"A Unique NFT collection of Submarines on Sui", b"SUIM", // symbol 100, // max_supply - receiver, // Royalty receiver + @0x6c86ac4a796204ea09a87b6130db0c38263c1890, // Royalty receiver tags, 100, // royalty_fee_bps false, // is_mutable @@ -30,34 +32,41 @@ module nft_protocol::suimarines { ctx, ); + let whitelisting = false; + let pricing = 1000; + fixed_price::create_single_market( witness, tx_context::sender(ctx), // admin collection_id, - receiver, + @0x6c86ac4a796204ea09a87b6130db0c38263c1890, true, // is_embedded - false, // whitelist - 100, // price + whitelisting, + pricing, ctx, ); } - public entry fun mint_nft_data( + public entry fun prepare_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint_authority: &mut MintAuthority, + sale_outlet: u64, + launchpad: &mut Slingshot, ctx: &mut TxContext, ) { - unique_nft::prepare_launchpad_mint( + unique_nft::prepare_launchpad_mint( name, description, url, attribute_keys, attribute_values, mint_authority, + sale_outlet, + launchpad, ctx, ); } diff --git a/sources/examples/suricannes.move b/sources/examples/suricannes.move new file mode 100644 index 00000000..37c7a610 --- /dev/null +++ b/sources/examples/suricannes.move @@ -0,0 +1,76 @@ +module nft_protocol::suricannes { + use std::vector; + + use sui::tx_context::{Self, TxContext}; + + // NFT Modules + use nft_protocol::c_nft; + use nft_protocol::std_collection; + use nft_protocol::collection::{MintAuthority}; + + // Market Modules + use nft_protocol::slingshot::Slingshot; + use nft_protocol::fixed_price::{Self, FixedPriceMarket}; + + struct SURICANNES has drop {} + + fun init(witness: SURICANNES, ctx: &mut TxContext) { + let tags: vector> = vector::empty(); + vector::push_back(&mut tags, b"Art"); + vector::push_back(&mut tags, b"PFP"); + + let collection_id = std_collection::mint( + b"Suricannes", + b"A Unique NFT collection of Hurricanes on Sui", + b"SURI", // symbol + 100, // max_supply + @0x6c86ac4a796204ea09a87b6130db0c38263c1890, // Royalty receiver + tags, + 100, // royalty_fee_bps + true, // is_mutable + b"Some extra data", + tx_context::sender(ctx), // mint authority + ctx, + ); + + let whitelisting = false; + let pricing = 1000; + + fixed_price::create_single_market( + witness, + tx_context::sender(ctx), // admin + collection_id, + @0x6c86ac4a796204ea09a87b6130db0c38263c1890, + false, // is_embedded + whitelisting, + pricing, + ctx, + ); + } + + public entry fun prepare_mint( + name: vector, + description: vector, + url: vector, + attribute_keys: vector>, + attribute_values: vector>, + max_supply: u64, + mint: &mut MintAuthority, + sale_outlet: u64, + launchpad: &mut Slingshot, + ctx: &mut TxContext, + ) { + c_nft::prepare_launchpad_mint( + name, + description, + url, + attribute_keys, + attribute_values, + max_supply, + mint, + sale_outlet, + launchpad, + ctx, + ); + } +} diff --git a/sources/launchpad/slingshot.move b/sources/launchpad/slingshot.move index 3c7aba33..0bd035fb 100644 --- a/sources/launchpad/slingshot.move +++ b/sources/launchpad/slingshot.move @@ -14,7 +14,7 @@ module nft_protocol::slingshot { use sui::tx_context::{Self, TxContext}; use sui::dynamic_object_field; - use nft_protocol::nft; + use nft_protocol::nft::{Self, Nft}; use nft_protocol::err; use nft_protocol::sale::{Self, Sale, NftCertificate}; @@ -137,8 +137,8 @@ module nft_protocol::slingshot { recipient: address, ctx: &mut TxContext, ) { - let nft: Nft = - dynamic_object_field::remove(&mut slingshot.id, nft_id); + // let nft: Nft = + // dynamic_object_field::remove(&mut slingshot.id, nft_id); assert!( object::id(&nft_data) == sale::nft_id(&certificate), diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index 1d372875..ecc1dd94 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -139,7 +139,7 @@ module nft_protocol::c_nft { /// and what the supply of those configurations are. /// /// To be called by the Witness Module deployed by NFT creator. - public fun prepare_launchpad_mint( + public fun prepare_launchpad_mint( name: vector, description: vector, url: vector, diff --git a/sources/nft/nfts/unique_nft.move b/sources/nft/nfts/unique_nft.move index 7fac66cc..9378be84 100644 --- a/sources/nft/nfts/unique_nft.move +++ b/sources/nft/nfts/unique_nft.move @@ -15,6 +15,8 @@ module nft_protocol::unique_nft { use sui::url::{Self, Url}; use nft_protocol::err; + use nft_protocol::sale; + use nft_protocol::slingshot::{Self, Slingshot}; use nft_protocol::collection::{Self, MintAuthority}; use nft_protocol::utils::{to_string_vector}; use nft_protocol::supply_policy; @@ -72,13 +74,15 @@ module nft_protocol::unique_nft { /// ought to be called 100 times in total to mint such objects. /// /// To be called by the Witness Module deployed by NFT creator. - public fun prepare_launchpad_mint( + public fun prepare_launchpad_mint( name: vector, description: vector, url: vector, attribute_keys: vector>, attribute_values: vector>, mint: &mut MintAuthority, + sale_outlet: u64, + launchpad: &mut Slingshot, ctx: &mut TxContext, ) { // Assert that it has regulated supply policy @@ -97,10 +101,21 @@ module nft_protocol::unique_nft { collection::increment_supply(mint, 1); + let data_uid = object::new(ctx); + let data_id = object::uid_to_inner(&data_uid); + mint_and_share_data( + data_uid, args, collection::mint_collection_id(mint), - ctx, + ); + + let sale = slingshot::sale_mut(launchpad, sale_outlet); + + sale::add_nft( + sale, + data_id, + 1 ); } @@ -322,12 +337,10 @@ module nft_protocol::unique_nft { } fun mint_and_share_data( + data_id: UID, args: MintArgs, collection_id: ID, - ctx: &mut TxContext, ) { - let data_id = object::new(ctx); - event::emit( MintDataEvent { object_id: object::uid_to_inner(&data_id), From 6ea74028b363bb121370e6d61a753c855352df8f Mon Sep 17 00:00:00 2001 From: Nuno Boavida <45330362+nmboavida@users.noreply.github.com> Date: Fri, 4 Nov 2022 15:41:19 +0000 Subject: [PATCH 31/35] fix mint functions --- sources/launchpad/slingshot.move | 1 + sources/nft/nfts/c_nft.move | 1 + 2 files changed, 2 insertions(+) diff --git a/sources/launchpad/slingshot.move b/sources/launchpad/slingshot.move index 5eb13ca9..3f197547 100644 --- a/sources/launchpad/slingshot.move +++ b/sources/launchpad/slingshot.move @@ -148,6 +148,7 @@ module nft_protocol::slingshot { let nft = nft::mint_nft_loose( object::id(&nft_data), + tx_context::sender(ctx), ctx, ); diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index 16abf4fd..206df136 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -339,6 +339,7 @@ module nft_protocol::c_nft { let nft = nft::mint_nft_loose( nft_data_id(nft_data), + tx_context::sender(ctx), ctx, ); From 22cd61ffcf42b4a4f33fd189d1b514bae18c4cf6 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Wed, 9 Nov 2022 16:54:42 +0100 Subject: [PATCH 32/35] Sorting imports --- sources/examples/collectibles.move | 7 +-- sources/examples/suimarines.move | 6 ++- sources/examples/suricannes.move | 76 ------------------------------ 3 files changed, 6 insertions(+), 83 deletions(-) delete mode 100644 sources/examples/suricannes.move diff --git a/sources/examples/collectibles.move b/sources/examples/collectibles.move index bb89ef6d..51f6806e 100644 --- a/sources/examples/collectibles.move +++ b/sources/examples/collectibles.move @@ -5,12 +5,9 @@ module nft_protocol::suinamis { use nft_protocol::collectible; use nft_protocol::collection::{MintAuthority}; - use nft_protocol::fixed_price; - use nft_protocol::std_collection; - - // Market Modules - use nft_protocol::slingshot::Slingshot; use nft_protocol::fixed_price::{Self, FixedPriceMarket}; + use nft_protocol::slingshot::Slingshot; + use nft_protocol::std_collection; struct SUINAMIS has drop {} diff --git a/sources/examples/suimarines.move b/sources/examples/suimarines.move index 74e0f4c1..633d1c60 100644 --- a/sources/examples/suimarines.move +++ b/sources/examples/suimarines.move @@ -1,11 +1,13 @@ module nft_protocol::suimarines { + use std::vector; + + use sui::tx_context::{Self, TxContext}; + use nft_protocol::collection::{MintAuthority}; use nft_protocol::fixed_price::{Self, FixedPriceMarket}; use nft_protocol::slingshot::Slingshot; use nft_protocol::std_collection; use nft_protocol::unique_nft; - use std::vector; - use sui::tx_context::{Self, TxContext}; struct SUIMARINES has drop {} diff --git a/sources/examples/suricannes.move b/sources/examples/suricannes.move deleted file mode 100644 index 37c7a610..00000000 --- a/sources/examples/suricannes.move +++ /dev/null @@ -1,76 +0,0 @@ -module nft_protocol::suricannes { - use std::vector; - - use sui::tx_context::{Self, TxContext}; - - // NFT Modules - use nft_protocol::c_nft; - use nft_protocol::std_collection; - use nft_protocol::collection::{MintAuthority}; - - // Market Modules - use nft_protocol::slingshot::Slingshot; - use nft_protocol::fixed_price::{Self, FixedPriceMarket}; - - struct SURICANNES has drop {} - - fun init(witness: SURICANNES, ctx: &mut TxContext) { - let tags: vector> = vector::empty(); - vector::push_back(&mut tags, b"Art"); - vector::push_back(&mut tags, b"PFP"); - - let collection_id = std_collection::mint( - b"Suricannes", - b"A Unique NFT collection of Hurricanes on Sui", - b"SURI", // symbol - 100, // max_supply - @0x6c86ac4a796204ea09a87b6130db0c38263c1890, // Royalty receiver - tags, - 100, // royalty_fee_bps - true, // is_mutable - b"Some extra data", - tx_context::sender(ctx), // mint authority - ctx, - ); - - let whitelisting = false; - let pricing = 1000; - - fixed_price::create_single_market( - witness, - tx_context::sender(ctx), // admin - collection_id, - @0x6c86ac4a796204ea09a87b6130db0c38263c1890, - false, // is_embedded - whitelisting, - pricing, - ctx, - ); - } - - public entry fun prepare_mint( - name: vector, - description: vector, - url: vector, - attribute_keys: vector>, - attribute_values: vector>, - max_supply: u64, - mint: &mut MintAuthority, - sale_outlet: u64, - launchpad: &mut Slingshot, - ctx: &mut TxContext, - ) { - c_nft::prepare_launchpad_mint( - name, - description, - url, - attribute_keys, - attribute_values, - max_supply, - mint, - sale_outlet, - launchpad, - ctx, - ); - } -} From 477b0b961a43a2783c1a24a4b8794e4d3d4e78a8 Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 21 Nov 2022 19:45:48 +0100 Subject: [PATCH 33/35] Fixing merge conflicts --- sources/safe/safe.move | 4 ++-- .../{trading => safe}/transfer_whitelist.move | 0 sources/utils/err.move | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) rename sources/{trading => safe}/transfer_whitelist.move (100%) diff --git a/sources/safe/safe.move b/sources/safe/safe.move index ca51f4ba..a1d1b0c5 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -94,7 +94,7 @@ module nft_protocol::safe { /// /// Otherwise, there's a risk of a race condition as multiple non-exclusive /// transfer caps can be created. - public entry fun create_transfer_cap( + public fun create_transfer_cap( nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, @@ -123,7 +123,7 @@ module nft_protocol::safe { /// Creates an irrevocable and exclusive transfer cap. /// /// Useful for trading contracts which cannot claim an NFT atomically. - public entry fun create_exlusive_transfer_cap( + public fun create_exlusive_transfer_cap( nft: ID, owner_cap: &OwnerCap, safe: &mut Safe, diff --git a/sources/trading/transfer_whitelist.move b/sources/safe/transfer_whitelist.move similarity index 100% rename from sources/trading/transfer_whitelist.move rename to sources/safe/transfer_whitelist.move diff --git a/sources/utils/err.move b/sources/utils/err.move index 72f6f9b2..46de63ba 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -134,36 +134,36 @@ module nft_protocol::err { // === Safe === public fun safe_cap_mismatch(): u64 { - return Prefix + 300 + return Prefix + 400 } public fun safe_does_not_contain_nft(): u64 { - return Prefix + 301 + return Prefix + 401 } public fun nft_exlusively_listed(): u64 { - return Prefix + 302 + return Prefix + 402 } public fun transfer_cap_nft_mismatch(): u64 { - return Prefix + 303 + return Prefix + 403 } public fun transfer_cap_expired(): u64 { - return Prefix + 304 + return Prefix + 404 } public fun safe_does_not_accept_deposits(): u64 { - return Prefix + 305 + return Prefix + 405 } // === Whitelist === public fun authority_not_whitelisted(): u64 { - return Prefix + 400 + return Prefix + 500 } public fun sender_not_collection_creator(): u64 { - return Prefix + 401 + return Prefix + 501 } } From a898a27887bac08efdb2aded09406f37e255fd2b Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 21 Nov 2022 19:47:50 +0100 Subject: [PATCH 34/35] Fixing wrong merge to main --- sources/launchpad/market/fixed_price.move | 8 ++---- sources/launchpad/sale.move | 31 +++++------------------ sources/nft/nfts/c_nft.move | 1 - sources/nft/nfts/collectible.move | 1 - sources/nft/nfts/unique_nft.move | 1 - 5 files changed, 9 insertions(+), 33 deletions(-) diff --git a/sources/launchpad/market/fixed_price.move b/sources/launchpad/market/fixed_price.move index 1f684f92..55580c65 100644 --- a/sources/launchpad/market/fixed_price.move +++ b/sources/launchpad/market/fixed_price.move @@ -35,8 +35,8 @@ module nft_protocol::fixed_price { // === Functions exposed to Witness Module === /// Creates a fixed price `Launchpad` sale. A sale can be simple or tiered, - /// that is, a tiered sale `Launchpad` has multiple `Sale` outlets in its - /// field `sales`. This funcitonality allows for the creation of tiered + /// that is, a tiered sale `Launchpad` has multiple `Sale` outlets in its + /// field `sales`. This funcitonality allows for the creation of tiered /// market sales by segregating NFTs by different sale segments /// (e.g. based on rarity, or preciousness). /// @@ -114,7 +114,6 @@ module nft_protocol::fixed_price { let launchpad_id = slingshot::id(slingshot); let receiver = slingshot::receiver(slingshot); - let collection_id = slingshot::collection_id(slingshot); let sale = slingshot::sale_mut(slingshot, tier_index); // Infer that sales is NOT whitelisted @@ -138,7 +137,6 @@ module nft_protocol::fixed_price { let certificate = sale::issue_nft_certificate( sale, launchpad_id, - collection_id, ctx ); @@ -167,7 +165,6 @@ module nft_protocol::fixed_price { let launchpad_id = slingshot::id(slingshot); let receiver = slingshot::receiver(slingshot); - let collection_id = slingshot::collection_id(slingshot); let sale = slingshot::sale_mut(slingshot, tier_index); // Infer that sales is whitelisted @@ -199,7 +196,6 @@ module nft_protocol::fixed_price { let certificate = sale::issue_nft_certificate( sale, launchpad_id, - collection_id, ctx ); diff --git a/sources/launchpad/sale.move b/sources/launchpad/sale.move index 2a50de60..d49b24fa 100644 --- a/sources/launchpad/sale.move +++ b/sources/launchpad/sale.move @@ -22,18 +22,12 @@ module nft_protocol::sale { id: UID, tier_index: u64, whitelisted: bool, - // Vector of all IDs owned by the slingshot along with a `u64` value - // representing the supply to be minted. - nfts: vector, + // Vector of all IDs owned by the slingshot + nfts: vector, queue: vector, market: Market, } - struct NftToSell has copy, drop, store { - id: ID, - supply: u64, - } - /// This object acts as an intermediate step between the payment /// and the transfer of the NFT. The user first has to call /// `buy_nft_certificate` which mints and transfers the `NftCertificate` to @@ -42,7 +36,6 @@ module nft_protocol::sale { struct NftCertificate has key, store { id: UID, launchpad_id: ID, - collection_id: ID, nft_id: ID, } @@ -98,16 +91,14 @@ module nft_protocol::sale { public fun issue_nft_certificate( sale: &mut Sale, launchpad_id: ID, - collection_id: ID, ctx: &mut TxContext, ): NftCertificate { - let nft_to_sell = pop_nft(sale); + let nft_id = pop_nft(sale); let certificate = NftCertificate { id: object::new(ctx), - launchpad_id, - collection_id, - nft_id: nft_to_sell.id, + launchpad_id: launchpad_id, + nft_id: nft_id, }; certificate @@ -119,7 +110,6 @@ module nft_protocol::sale { let NftCertificate { id, launchpad_id: _, - collection_id: _, nft_id: _, } = certificate; @@ -130,10 +120,9 @@ module nft_protocol::sale { public fun add_nft( sale: &mut Sale, id: ID, - supply: u64, ) { let nfts = &mut sale.nfts; - vector::push_back(nfts, NftToSell {id, supply}); + vector::push_back(nfts, id); } /// Pops an NFT's ID from the `nfts` field in `Sale` object @@ -141,7 +130,7 @@ module nft_protocol::sale { /// TODO: Need to push the ID to the queue fun pop_nft( sale: &mut Sale, - ): NftToSell { + ): ID { let nfts = &mut sale.nfts; assert!(!vector::is_empty(nfts), err::sale_outlet_has_no_nfts_to_sell()); vector::pop_back(nfts) @@ -195,10 +184,4 @@ module nft_protocol::sale { ): bool { sale.whitelisted } - - public fun collection_id( - certificate: &NftCertificate, - ): ID { - certificate.collection_id - } } diff --git a/sources/nft/nfts/c_nft.move b/sources/nft/nfts/c_nft.move index 206df136..6190a4cf 100644 --- a/sources/nft/nfts/c_nft.move +++ b/sources/nft/nfts/c_nft.move @@ -183,7 +183,6 @@ module nft_protocol::c_nft { sale::add_nft( sale, data_id, - max_supply ); } diff --git a/sources/nft/nfts/collectible.move b/sources/nft/nfts/collectible.move index 1ef75266..753788af 100644 --- a/sources/nft/nfts/collectible.move +++ b/sources/nft/nfts/collectible.move @@ -120,7 +120,6 @@ module nft_protocol::collectible { sale::add_nft( sale, data_id, - max_supply ); } diff --git a/sources/nft/nfts/unique_nft.move b/sources/nft/nfts/unique_nft.move index f708ce9e..c0c6c1c5 100644 --- a/sources/nft/nfts/unique_nft.move +++ b/sources/nft/nfts/unique_nft.move @@ -115,7 +115,6 @@ module nft_protocol::unique_nft { sale::add_nft( sale, data_id, - 1 ); } From 1909eda68778a42e2af9dfaf2fef53d79da6692f Mon Sep 17 00:00:00 2001 From: porkbrain Date: Mon, 21 Nov 2022 19:53:37 +0100 Subject: [PATCH 35/35] Simplifying Whitelist by removing a generic --- sources/nft/nft.move | 10 +++---- sources/safe/safe.move | 8 +++--- sources/safe/transfer_whitelist.move | 42 ++++++++++++++++++++-------- sources/utils/err.move | 4 +++ 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/sources/nft/nft.move b/sources/nft/nft.move index 194060ec..605b105b 100644 --- a/sources/nft/nft.move +++ b/sources/nft/nft.move @@ -172,24 +172,24 @@ module nft_protocol::nft { /// If the authority was whitelisted by the creator, we transfer /// the NFT to the recipient address. - public fun transfer( + public fun transfer( nft: Nft, recipient: address, authority: Auth, - whitelist: &Whitelist, + whitelist: &Whitelist, ) { change_logical_owner(&mut nft, recipient, authority, whitelist); transfer::transfer(nft, recipient); } /// Whitelisted contracts (by creator) can change logical owner of an NFT. - public fun change_logical_owner( + public fun change_logical_owner( nft: &mut Nft, recipient: address, authority: Auth, - whitelist: &Whitelist, + whitelist: &Whitelist, ) { - let is_ok = transfer_whitelist::can_be_transferred( + let is_ok = transfer_whitelist::can_be_transferred( authority, whitelist, ); diff --git a/sources/safe/safe.move b/sources/safe/safe.move index a1d1b0c5..ee834356 100644 --- a/sources/safe/safe.move +++ b/sources/safe/safe.move @@ -225,11 +225,11 @@ module nft_protocol::safe { /// If the NFT is not exlusively listed, it can happen that the transfer /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. - public fun transfer_nft_to_recipient( + public fun transfer_nft_to_recipient( transfer_cap: TransferCap, recipient: address, authority: Auth, - whitelist: &Whitelist, + whitelist: &Whitelist, safe: &mut Safe, ) { let nft = get_nft_for_transfer_(transfer_cap, safe); @@ -244,11 +244,11 @@ module nft_protocol::safe { /// If the NFT is not exlusively listed, it can happen that the transfer /// cap is no longer valid. The NFT could've been traded or the trading cap /// revoked. - public fun transfer_nft_to_safe( + public fun transfer_nft_to_safe( transfer_cap: TransferCap, recipient: address, authority: Auth, - whitelist: &Whitelist, + whitelist: &Whitelist, source: &mut Safe, target: &mut Safe, ctx: &mut TxContext, diff --git a/sources/safe/transfer_whitelist.move b/sources/safe/transfer_whitelist.move index e47024d7..44716965 100644 --- a/sources/safe/transfer_whitelist.move +++ b/sources/safe/transfer_whitelist.move @@ -21,8 +21,11 @@ module nft_protocol::transfer_whitelist { use sui::tx_context::{Self, TxContext}; use sui::vec_set::{Self, VecSet}; - struct Whitelist has key, store { + struct Whitelist has key, store { id: UID, + /// We don't store it as generic because then it has to be propagated + /// around and it's very unergonomic. + admin_witness: TypeName, /// Which collections does this whitelist apply to? /// /// We use reflection to avoid generics. @@ -37,9 +40,10 @@ module nft_protocol::transfer_whitelist { public fun create( _witness: Admin, ctx: &mut TxContext - ): Whitelist { + ): Whitelist { Whitelist { id: object::new(ctx), + admin_witness: type_name::get(), collections: vec_set::empty(), authorities: option::none(), } @@ -55,10 +59,11 @@ module nft_protocol::transfer_whitelist { public fun insert_collection( _whitelist_witness: Admin, collection: &Collection, - list: &mut Whitelist, + list: &mut Whitelist, ctx: &mut TxContext, ) { assert_is_creator(collection, ctx); + assert_admin_witness(list); vec_set::insert(&mut list.collections, type_name::get()); } @@ -68,9 +73,9 @@ module nft_protocol::transfer_whitelist { /// /// It's always the creator's right to decide at any point what authorities /// can transfer NFTs of that collection. - public fun remove_itself( + public fun remove_itself( collection: &Collection, - list: &mut Whitelist, + list: &mut Whitelist, ctx: &mut TxContext, ) { assert_is_creator(collection, ctx); @@ -81,16 +86,18 @@ module nft_protocol::transfer_whitelist { /// The whitelist owner can remove any collection at any point. public fun remove_collection( _whitelist_witness: Admin, - list: &mut Whitelist, + list: &mut Whitelist, ) { + assert_admin_witness(list); vec_set::remove(&mut list.collections, &type_name::get()); } /// Removes all collections from this list. public fun clear_collections( _whitelist_witness: Admin, - list: &mut Whitelist, + list: &mut Whitelist, ) { + assert_admin_witness(list); list.collections = vec_set::empty(); } @@ -98,8 +105,10 @@ module nft_protocol::transfer_whitelist { /// whitelist authority (via witness.) public fun insert_authority( _whitelist_witness: Admin, - list: &mut Whitelist, + list: &mut Whitelist, ) { + assert_admin_witness(list); + if (option::is_none(&list.authorities)) { list.authorities = option::some( vec_set::singleton(type_name::get()) @@ -116,8 +125,10 @@ module nft_protocol::transfer_whitelist { /// authority from their list. public fun remove_authority( _whitelist_witness: Admin, - list: &mut Whitelist, + list: &mut Whitelist, ) { + assert_admin_witness(list); + vec_set::remove( option::borrow_mut(&mut list.authorities), &type_name::get(), @@ -126,9 +137,9 @@ module nft_protocol::transfer_whitelist { /// Checks whether given authority witness is in the whitelist, and also /// whether given collection witness (CW) is in the whitelist. - public fun can_be_transferred( + public fun can_be_transferred( _authority_witness: Auth, - whitelist: &Whitelist, + whitelist: &Whitelist, ): bool { if (option::is_none(&whitelist.authorities)) { return true @@ -152,4 +163,13 @@ module nft_protocol::transfer_whitelist { err::sender_not_collection_creator(), ); } + + fun assert_admin_witness( + list: &Whitelist, + ) { + assert!( + type_name::get() == list.admin_witness, + err::sender_not_whitelist_admin(), + ); + } } diff --git a/sources/utils/err.move b/sources/utils/err.move index 46de63ba..866c6025 100644 --- a/sources/utils/err.move +++ b/sources/utils/err.move @@ -166,4 +166,8 @@ module nft_protocol::err { public fun sender_not_collection_creator(): u64 { return Prefix + 501 } + + public fun sender_not_whitelist_admin(): u64 { + return Prefix + 502 + } }