--- a/README.md +++ b/README.md @@ -142,6 +142,12 @@ "enable_println": "bool", "max_subject_len": "u32" }, + "AccessMode": { + "_enum": [ + "Normal", + "WhiteList" + ] + }, "CollectionMode": { "_enum": { "Invalid": null, @@ -181,12 +187,13 @@ "CollectionType": { "Owner": "AccountId", "Mode": "CollectionMode", - "Access": "u8", + "Access": "AccessMode", "DecimalPoints": "u32", "Name": "Vec", "Description": "Vec", "TokenPrefix": "Vec", "CustomDataSize": "u32", + "MintMode": "bool", "OffchainSchema": "Vec", "Sponsor": "AccountId", "UnconfirmedSponsor": "AccountId" @@ -196,4 +203,5 @@ "LookupSource": "AccountId", "Weight": "u64" } + ``` \ No newline at end of file --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -97,6 +97,7 @@ pub description: Vec, // 256 include null escape char pub token_prefix: Vec, // 16 include null escape char pub custom_data_size: u32, + pub mint_mode: bool, pub offchain_schema: Vec, pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship @@ -178,9 +179,6 @@ pub NftItemList get(fn nft_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType; pub FungibleItemList get(fn fungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType; pub ReFungibleItemList get(fn refungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType; - - // Active vesting list - // pub VestingList get(fn vesting): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => VestingItem; /// Index list pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec; @@ -209,7 +207,7 @@ fn on_initialize(now: T::BlockNumber) -> Weight { - if ChainVersion::get() == 0 + if ChainVersion::get() < 2 { let value = NextCollectionID::get(); CreatedCollectionCount::put(value); @@ -224,11 +222,11 @@ // @param customDataSz size of custom data in each collection item // returns collection ID #[weight = 0] - pub fn create_collection( origin, - collection_name: Vec, - collection_description: Vec, - token_prefix: Vec, - mode: CollectionMode) -> DispatchResult { + pub fn create_collection(origin, + collection_name: Vec, + collection_description: Vec, + token_prefix: Vec, + mode: CollectionMode) -> DispatchResult { // Anyone can create a collection let who = ensure_signed(origin)?; @@ -271,6 +269,7 @@ owner: who.clone(), name: name, mode: mode.clone(), + mint_mode: false, access: AccessMode::Normal, description: description, decimal_points: decimal_points, @@ -309,6 +308,73 @@ } #[weight = 0] + pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{ + + let sender = ensure_signed(origin)?; + Self::check_owner_or_admin_permissions(collection_id, sender)?; + + let mut white_list_collection: Vec; + if >::contains_key(collection_id) { + white_list_collection = >::get(collection_id); + if !white_list_collection.contains(&address.clone()) + { + white_list_collection.push(address.clone()); + } + } + else { + white_list_collection = Vec::new(); + white_list_collection.push(address.clone()); + } + + >::insert(collection_id, white_list_collection); + Ok(()) + } + + #[weight = 0] + pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{ + + let sender = ensure_signed(origin)?; + Self::check_owner_or_admin_permissions(collection_id, sender)?; + + if >::contains_key(collection_id) { + let mut white_list_collection = >::get(collection_id); + if white_list_collection.contains(&address.clone()) + { + white_list_collection.retain(|i| *i != address.clone()); + >::insert(collection_id, white_list_collection); + } + } + + Ok(()) + } + + #[weight = 0] + pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult + { + let sender = ensure_signed(origin)?; + + Self::check_owner_permissions(collection_id, sender)?; + let mut target_collection = >::get(collection_id); + target_collection.access = mode; + >::insert(collection_id, target_collection); + + Ok(()) + } + + #[weight = 0] + pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult + { + let sender = ensure_signed(origin)?; + + Self::check_owner_permissions(collection_id, sender)?; + let mut target_collection = >::get(collection_id); + target_collection.mint_mode = mint_permission; + >::insert(collection_id, target_collection); + + Ok(()) + } + + #[weight = 0] pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -405,9 +471,14 @@ pub fn create_item(origin, collection_id: u64, properties: Vec, owner: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; + Self::collection_exists(collection_id)?; let target_collection = >::get(collection_id); - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; + if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + ensure!(target_collection.mint_mode == true, "Collection is not in mint mode"); + Self::check_white_list(collection_id, owner.clone())?; + } + match target_collection.mode { CollectionMode::NFT(_) => { @@ -469,10 +540,13 @@ pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult { let sender = ensure_signed(origin)?; + Self::collection_exists(collection_id)?; let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id); if !item_owner { - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; + if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + Self::check_white_list(collection_id, sender.clone())?; + } } let target_collection = >::get(collection_id); @@ -494,11 +568,10 @@ pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id), "Only item owner can call transfer method"); - + Self::check_white_list(collection_id, sender.clone())?; + Self::check_white_list(collection_id, recipient.clone())?; let target_collection = >::get(collection_id); - // TODO: implement other modes match target_collection.mode { CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?, @@ -518,7 +591,10 @@ // amount param stub let amount = 100000000; - ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id), "Only item owner can call transfer method"); + let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id); + if !item_owner { + Self::check_white_list(collection_id, approved.clone())?; + } let list_exists = >::contains_key(collection_id, (item_id, sender.clone())); if list_exists { @@ -545,22 +621,21 @@ let sender = ensure_signed(origin)?; let approved_list_exists = >::contains_key(collection_id, (item_id, from.clone())); - if approved_list_exists - { - let list_itm = >::get(collection_id, (item_id, from.clone())); - let opt_item = list_itm.iter().find(|i| i.approved == sender.clone()); - ensure!(opt_item.is_some(), "No approve found"); - ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved"); - // remove approve - let approve_list: Vec> = >::get(collection_id, (item_id, from.clone())) - .into_iter().filter(|i| i.approved != sender.clone()).collect(); - >::insert(collection_id, (item_id, from.clone()), approve_list); - } - else - { - Self::check_owner_or_admin_permissions(collection_id, sender)?; - } + ensure!(approved_list_exists, "Only approved addresses can call this method"); + + Self::check_white_list(collection_id, from.clone())?; + Self::check_white_list(collection_id, recipient.clone())?; + + let list_itm = >::get(collection_id, (item_id, from.clone())); + let opt_item = list_itm.iter().find(|i| i.approved == sender.clone()); + ensure!(opt_item.is_some(), "No approve found"); + ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved"); + + // remove approve + let approve_list: Vec> = >::get(collection_id, (item_id, from.clone())) + .into_iter().filter(|i| i.approved != sender.clone()).collect(); + >::insert(collection_id, (item_id, from.clone()), approve_list); let target_collection = >::get(collection_id); @@ -675,11 +750,7 @@ Ok(()) } - fn burn_refungible_item( - collection_id: u64, - item_id: u64, - owner: T::AccountId, - ) -> DispatchResult { + fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), "Item does not exists" @@ -770,25 +841,27 @@ Ok(()) } - fn check_owner_or_admin_permissions( - collection_id: u64, - subject: T::AccountId, - ) -> DispatchResult { - Self::collection_exists(collection_id)?; + fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool { let target_collection = >::get(collection_id); - let is_owner = subject == target_collection.owner; - - let no_perm_mes = "You do not have permissions to modify this collection"; + let mut result: bool = subject == target_collection.owner; let exists = >::contains_key(collection_id); - if !is_owner { - ensure!(exists, no_perm_mes); - ensure!( - >::get(collection_id).contains(&subject), - no_perm_mes - ); + if !result & exists { + if >::get(collection_id).contains(&subject) { + result = true + } } + + result + } + + fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult { + + Self::collection_exists(collection_id)?; + let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone()); + + ensure!(result, "You do not have permissions to modify this collection"); Ok(()) } @@ -812,6 +885,16 @@ } } + fn check_white_list(collection_id: u64, address: T::AccountId) -> DispatchResult { + + let mes = "Address is not in white list"; + ensure!(>::contains_key(collection_id), mes); + let wl = >::get(collection_id); + ensure!(wl.contains(&address.clone()), mes); + + Ok(()) + } + fn transfer_fungible( collection_id: u64, item_id: u64, @@ -819,6 +902,12 @@ owner: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { + + ensure!( + >::contains_key(collection_id, item_id), + "Item not exists" + ); + let full_item = >::get(collection_id, item_id); let amount = full_item.value; @@ -903,6 +992,12 @@ owner: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { + + ensure!( + >::contains_key(collection_id, item_id), + "Item not exists" + ); + let full_item = >::get(collection_id, item_id); let item = full_item .owner @@ -983,6 +1078,12 @@ sender: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { + + ensure!( + >::contains_key(collection_id, item_id), + "Item not exists" + ); + let mut item = >::get(collection_id, item_id); ensure!( --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -1,6 +1,6 @@ // Tests to be written here use crate::mock::*; -use crate::{ApprovePermissions, CollectionMode, Ownership}; +use crate::{ApprovePermissions, CollectionMode, AccessMode, Ownership}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -321,10 +321,11 @@ assert_eq!(TemplateModule::balance_count(1, 1), 1); assert_eq!(TemplateModule::address_tokens(1, 1), [1]); - assert_noop!( - TemplateModule::transfer_from(origin2.clone(), 1, 3, 1, 1, 1), - "You do not have permissions to modify this collection" - ); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3)); // do approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); @@ -390,10 +391,11 @@ assert_eq!(TemplateModule::balance_count(1, 1), 1000); assert_eq!(TemplateModule::address_tokens(1, 1), [1]); - assert_noop!( - TemplateModule::transfer_from(origin2.clone(), 1, 3, 1, 1, 1), - "You do not have permissions to modify this collection" - ); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3)); // do approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); @@ -461,10 +463,11 @@ assert_eq!(TemplateModule::balance_count(1, 1), 1000); assert_eq!(TemplateModule::address_tokens(1, 1), [1]); - assert_noop!( - TemplateModule::transfer_from(origin2.clone(), 1, 3, 1, 1, 1), - "You do not have permissions to modify this collection" - ); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3)); // do approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); @@ -573,7 +576,6 @@ let mode: CollectionMode = CollectionMode::NFT(2000); let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); assert_ok!(TemplateModule::create_collection( origin1.clone(), col_name1.clone(), @@ -583,7 +585,7 @@ )); assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); assert_ok!(TemplateModule::create_item( - origin2.clone(), + origin1.clone(), 1, [1, 2, 3].to_vec(), 1 @@ -614,7 +616,6 @@ let mode: CollectionMode = CollectionMode::Fungible(3); let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); assert_ok!(TemplateModule::create_collection( origin1.clone(), col_name1.clone(), @@ -624,7 +625,7 @@ )); assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); assert_ok!(TemplateModule::create_item( - origin2.clone(), + origin1.clone(), 1, [].to_vec(), 1 @@ -661,6 +662,11 @@ token_prefix1.clone(), mode )); + + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1)); + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); assert_ok!(TemplateModule::create_item( origin2.clone(), @@ -928,6 +934,13 @@ // approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1))[0].approved, 2); + + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3)); + assert_ok!(TemplateModule::transfer_from( origin2.clone(), 1, --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -111,7 +111,7 @@ spec_name: create_runtime_str!("nft"), impl_name: create_runtime_str!("nft"), authoring_version: 1, - spec_version: 1, + spec_version: 2, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,