--- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -1,8 +1,9 @@ -use nft_runtime::{ - AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig, - SystemConfig, WASM_BINARY, -}; -use nft_runtime::{ContractsConfig, ContractsSchedule}; +// use nft_runtime::{ +// AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig, +// SystemConfig, WASM_BINARY, +// }; +// use nft_runtime::{ContractsConfig, ContractsSchedule, NftConfig, CollectionType}; +use nft_runtime::*; use sc_service::ChainType; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{sr25519, Pair, Public}; @@ -128,6 +129,25 @@ .collect(), }), sudo: Some(SudoConfig { key: root_key }), + nft: Some(NftConfig { + collection: vec![(1, CollectionType { + owner: get_account_id_from_seed::("Alice"), + mode: CollectionMode::NFT(50), + access: AccessMode::Normal, + decimal_points: 0, + name: vec!(), + description: vec!(), + token_prefix: vec!(), + custom_data_size: 50, + mint_mode: false, + offchain_schema: vec!(), + sponsor: get_account_id_from_seed::("Alice"), + unconfirmed_sponsor: get_account_id_from_seed::("Alice"), + })], + nft_item_id: vec!(), + fungible_item_id: vec!(), + refungible_item_id: vec!(), + }), contracts: Some(ContractsConfig { current_schedule: ContractsSchedule { enable_println, --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -1,7 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] -/// For more guidance on Substrate FRAME, see the example pallet -/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs +#[cfg(feature = "std")] +pub use serde::*; + use codec::{Decode, Encode}; pub use frame_support::{ construct_runtime, decl_event, decl_module, decl_storage, @@ -32,7 +33,6 @@ }, FixedPointOperand, FixedU128, }; -use sp_std::prelude::*; #[cfg(test)] mod mock; @@ -40,7 +40,11 @@ #[cfg(test)] mod tests; -#[derive(Encode, Decode, Debug, Eq, Clone, PartialEq)] +// Structs +// #region + +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum CollectionMode { Invalid, // custom data size @@ -62,7 +66,8 @@ } } -#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum AccessMode { Normal, WhiteList, @@ -79,15 +84,15 @@ } } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Ownership { pub owner: AccountId, pub fraction: u128, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct CollectionType { pub owner: AccountId, pub mode: CollectionMode, @@ -103,46 +108,46 @@ pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct CollectionAdminsType { pub admin: AccountId, pub collection_id: u64, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct NftItemType { pub collection: u64, pub owner: AccountId, pub data: Vec, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct FungibleItemType { pub collection: u64, pub owner: AccountId, pub value: u128, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ReFungibleItemType { pub collection: u64, pub owner: Vec>, pub data: Vec, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ApprovePermissions { pub approved: AccountId, pub amount: u64, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct VestingItem { pub sender: AccountId, pub recipient: AccountId, @@ -156,6 +161,8 @@ type Event: From> + Into<::Event>; } +// #endregion + decl_storage! { trait Store for Module as Nft { @@ -165,7 +172,7 @@ ChainVersion: u64; ItemListIndex: map hasher(blake2_128_concat) u64 => u64; - pub Collection get(fn collection): map hasher(identity) u64 => CollectionType; + pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType; pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec; pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec; @@ -176,9 +183,9 @@ pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec>; /// Item collections - 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; + pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType; + pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType; + pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType; /// Index list pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec; @@ -187,6 +194,26 @@ pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId; pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId; } + add_extra_genesis { + build(|config: &GenesisConfig| { + // Modification of storage + for (_num, _c) in &config.collection { + >::init_collection(_c); + } + + for (_num, _q, _i) in &config.nft_item_id { + >::init_nft_token(_i); + } + + for (_num, _q, _i) in &config.fungible_item_id { + >::init_fungible_token(_i); + } + + for (_num, _q, _i) in &config.refungible_item_id { + >::init_refungible_token(_i); + } + }) + } } decl_event!( @@ -541,14 +568,16 @@ 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 - { - if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { - Self::check_white_list(collection_id, sender.clone())?; - } + + // Transfer permissions check + let target_collection = >::get(collection_id); + ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || + Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + "Only item owner, collection owner and admins can modify item"); + + if target_collection.access == AccessMode::WhiteList { + Self::check_white_list(collection_id, sender.clone())?; } - let target_collection = >::get(collection_id); match target_collection.mode { @@ -568,10 +597,18 @@ pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_white_list(collection_id, sender.clone())?; - Self::check_white_list(collection_id, recipient.clone())?; + + // Transfer permissions check let target_collection = >::get(collection_id); + ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || + Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + "Only item owner, collection owner and admins can modify item"); + if target_collection.access == AccessMode::WhiteList { + Self::check_white_list(collection_id, sender.clone())?; + Self::check_white_list(collection_id, recipient.clone())?; + } + match target_collection.mode { CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?, @@ -588,14 +625,20 @@ let sender = ensure_signed(origin)?; - // amount param stub - let amount = 100000000; + // Transfer permissions check + let target_collection = >::get(collection_id); + ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || + Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + "Only item owner, collection owner and admins can approve"); - let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id); - if !item_owner { + if target_collection.access == AccessMode::WhiteList { + Self::check_white_list(collection_id, sender.clone())?; Self::check_white_list(collection_id, approved.clone())?; } + // amount param stub + let amount = 100000000; + let list_exists = >::contains_key(collection_id, (item_id, sender.clone())); if list_exists { @@ -620,24 +663,31 @@ pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult { let sender = ensure_signed(origin)?; - let approved_list_exists = >::contains_key(collection_id, (item_id, from.clone())); + let mut appoved_transfer = false; - ensure!(approved_list_exists, "Only approved addresses can call this method"); + // Check approve + if >::contains_key(collection_id, (item_id, from.clone())) { + let list_itm = >::get(collection_id, (item_id, from.clone())); + let opt_item = list_itm.iter().find(|i| i.approved == sender.clone()); + appoved_transfer = opt_item.is_some(); + ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved"); + } - Self::check_white_list(collection_id, from.clone())?; - Self::check_white_list(collection_id, recipient.clone())?; + // Transfer permissions check + let target_collection = >::get(collection_id); + ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + "Only item owner, collection owner and admins can modify items"); - 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"); + if target_collection.access == AccessMode::WhiteList { + Self::check_white_list(collection_id, sender.clone())?; + Self::check_white_list(collection_id, recipient.clone())?; + } // 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); match target_collection.mode { @@ -1115,6 +1165,80 @@ Ok(()) } + fn init_collection(item: &CollectionType){ + + // check params + assert!(item.decimal_points <= 4, "decimal_points parameter must be lower than 4"); + assert!(item.name.len() <= 64, "Collection name can not be longer than 63 char"); + assert!(item.name.len() <= 256, "Collection description can not be longer than 255 char"); + assert!(item.token_prefix.len() <= 16, "Token prefix can not be longer than 15 char"); + + // Generate next collection ID + let next_id = CreatedCollectionCount::get() + .checked_add(1) + .expect("collection id error"); + + CreatedCollectionCount::put(next_id); + } + + fn init_nft_token(item: &NftItemType){ + + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + + let item_owner = item.owner.clone(); + let collection_id = item.collection.clone(); + Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap(); + + ::insert(collection_id, current_index); + + // Update balance + let new_balance = >::get(collection_id, item_owner.clone()) + .checked_add(1) + .unwrap(); + >::insert(collection_id, item_owner.clone(), new_balance); + } + + fn init_fungible_token(item: &FungibleItemType){ + + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + let owner = item.owner.clone(); + let value = item.value as u64; + + Self::add_token_index(item.collection, current_index, owner.clone()).unwrap(); + + ::insert(item.collection, current_index); + + // Update balance + let new_balance = >::get(item.collection, owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(item.collection, owner.clone(), new_balance); + } + + fn init_refungible_token(item: &ReFungibleItemType){ + + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + + let value = item.owner.first().unwrap().fraction as u64; + let owner = item.owner.first().unwrap().owner.clone(); + + Self::add_token_index(item.collection, current_index, owner.clone()).unwrap(); + + ::insert(item.collection, current_index); + + // Update balance + let new_balance = >::get(item.collection, owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(item.collection, owner.clone(), new_balance); + } + fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult { let list_exists = >::contains_key(collection_id, owner.clone()); if list_exists { @@ -1169,6 +1293,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// // Economic models +// #region /// Fee multiplier. pub type Multiplier = FixedU128; @@ -1363,3 +1488,4 @@ Ok(()) } } +// #endregion \ No newline at end of file --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -3,6 +3,8 @@ use crate::{ApprovePermissions, CollectionMode, AccessMode, Ownership}; use frame_support::{assert_noop, assert_ok}; +// Use cases tests region +// #region #[test] fn create_nft_item() { new_test_ext().execute_with(|| { @@ -330,7 +332,7 @@ // do approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); - assert_ok!(TemplateModule::approve(origin1.clone(), 10, 1, 1)); + assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 2); assert_eq!( TemplateModule::approved(1, (1, 1))[0], @@ -400,7 +402,7 @@ // do approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); - assert_ok!(TemplateModule::approve(origin1.clone(), 10, 1, 1)); + assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 2); assert_eq!( TemplateModule::approved(1, (1, 1))[0], @@ -427,7 +429,7 @@ assert_eq!( TemplateModule::approved(1, (1, 1))[0], ApprovePermissions { - approved: 10, + approved: 3, amount: 100000000 } ); @@ -472,7 +474,7 @@ // do approve assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); - assert_ok!(TemplateModule::approve(origin1.clone(), 10, 1, 1)); + assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1)); assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 2); assert_eq!( TemplateModule::approved(1, (1, 1))[0], @@ -499,7 +501,7 @@ assert_eq!( TemplateModule::approved(1, (1, 1))[0], ApprovePermissions { - approved: 10, + approved: 3, amount: 100000000 } ); @@ -955,3 +957,833 @@ assert_eq!(TemplateModule::balance_count(1, 2), 1); }); } + +// #endregion + +// Coverage tests region +// #region + +#[test] +fn owner_can_add_address_to_white_list() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + let origin1 = Origin::signed(1); + + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_eq!(TemplateModule::white_list(1)[0], 2); + }); +} + +#[test] +fn admin_can_add_address_to_white_list() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_to_white_list(origin2.clone(), 1, 3)); + assert_eq!(TemplateModule::white_list(1)[0], 3); + }); +} + +#[test] +fn nonprivileged_user_cannot_add_address_to_white_list() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_noop!(TemplateModule::add_to_white_list(origin2.clone(), 1, 3), "You do not have permissions to modify this collection"); + }); +} + +#[test] +fn nobody_can_add_address_to_white_list_of_nonexisting_collection() { + new_test_ext().execute_with(|| { + + let origin1 = Origin::signed(1); + assert_noop!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2), "This collection does not exist"); + }); +} + +#[test] +fn nobody_can_add_address_to_white_list_of_deleted_collection() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + let origin1 = Origin::signed(1); + + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1)); + assert_noop!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2), "This collection does not exist"); + }); +} + +// If address is already added to white list, nothing happens +#[test] +fn address_is_already_added_to_white_list() { + new_test_ext().execute_with(|| { + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + let origin1 = Origin::signed(1); + + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_eq!(TemplateModule::white_list(1)[0], 2); + assert_eq!(TemplateModule::white_list(1).len(), 1); + }); +} + +#[test] +fn owner_can_remove_address_from_white_list() { + new_test_ext().execute_with(|| { + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + let origin1 = Origin::signed(1); + + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2)); + assert_eq!(TemplateModule::white_list(1).len(), 0); + }); +} + +#[test] +fn admin_can_remove_address_from_white_list() { + new_test_ext().execute_with(|| { + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3)); + assert_ok!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 3)); + assert_eq!(TemplateModule::white_list(1).len(), 0); + }); +} + +#[test] +fn nonprivileged_user_cannot_remove_address_from_white_list() { + new_test_ext().execute_with(|| { + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_noop!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 2), "You do not have permissions to modify this collection"); + assert_eq!(TemplateModule::white_list(1)[0], 2); + }); +} + +#[test] +fn nobody_can_remove_address_from_white_list_of_nonexisting_collection() { + new_test_ext().execute_with(|| { + + let origin1 = Origin::signed(1); + assert_noop!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2), "This collection does not exist"); + }); +} + +#[test] +fn nobody_can_remove_address_from_white_list_of_deleted_collection() { + new_test_ext().execute_with(|| { + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1)); + assert_noop!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 2), "This collection does not exist"); + assert_eq!(TemplateModule::white_list(1).len(), 0); + }); +} + +// If address is already removed from white list, nothing happens +#[test] +fn address_is_already_removed_from_white_list() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + let origin1 = Origin::signed(1); + + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2)); + assert_eq!(TemplateModule::white_list(1).len(), 0); + }); +} + +// If Public Access mode is set to WhiteList, tokens can’t be transferred from a non-whitelisted address with transfer or transferFrom (2 tests) +#[test] +fn white_list_test_1() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + + assert_noop!(TemplateModule::transfer( + origin1.clone(), + 3, + 1, + 1, + 1 + ), "Address is not in white list"); + }); +} + +#[test] +fn white_list_test_2() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + 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)); + + // do approve + assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1)); + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); + + assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 1)); + + assert_noop!(TemplateModule::transfer_from( + origin1.clone(), + 1, + 3, + 1, + 1, + 1 + ), "Address is not in white list"); + }); +} + +// If Public Access mode is set to WhiteList, tokens can’t be transferred to a non-whitelisted address with transfer or transferFrom (2 tests) +#[test] +fn white_list_test_3() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1)); + + assert_noop!(TemplateModule::transfer( + origin1.clone(), + 3, + 1, + 1, + 1 + ), "Address is not in white list"); + }); +} + +#[test] +fn white_list_test_4() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + 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)); + + // do approve + assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1)); + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); + + assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2)); + + assert_noop!(TemplateModule::transfer_from( + origin1.clone(), + 1, + 3, + 1, + 1, + 1 + ), "Address is not in white list"); + }); +} + +// If Public Access mode is set to WhiteList, tokens can’t be destroyed by a non-whitelisted address (even if it owned them before enabling WhiteList mode) +#[test] +fn white_list_test_5() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_noop!(TemplateModule::burn_item(origin1.clone(), 1, 1), "Address is not in white list"); + }); +} + +// If Public Access mode is set to WhiteList, oken transfers can’t be Approved by a non-whitelisted address (see Approve method). +#[test] +fn white_list_test_6() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + + // do approve + assert_noop!(TemplateModule::approve(origin1.clone(), 1, 1, 1), "Address is not in white list"); + }); +} + +// If Public Access mode is set to WhiteList, tokens can be transferred from a whitelisted address with transfer or transferFrom (2 tests) and +// tokens can be transferred from a whitelisted address with transfer or transferFrom (2 tests) +#[test] +fn white_list_test_7() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + 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::transfer( + origin1.clone(), + 2, + 1, + 1, + 1 + )); + }); +} + +#[test] +fn white_list_test_8() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + assert_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + + 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)); + + // do approve + assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1)); + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); + + assert_ok!(TemplateModule::transfer_from( + origin1.clone(), + 1, + 2, + 1, + 1, + 1 + )); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens can be created by owner. +#[test] +fn white_list_test_9() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false)); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens can be created by admin. +#[test] +fn white_list_test_10() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false)); + + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); + + assert_ok!(TemplateModule::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 2 + )); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens cannot be created by non-privileged and white listed address. +#[test] +fn white_list_test_11() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + + assert_noop!(TemplateModule::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 2 + ), "Collection is not in mint mode"); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens cannot be created by non-privileged and non-white listed address. +#[test] +fn white_list_test_12() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false)); + + assert_noop!(TemplateModule::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 2 + ), "Collection is not in mint mode"); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens can be created by owner. +#[test] +fn white_list_test_13() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens can be created by admin. +#[test] +fn white_list_test_14() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); + + assert_ok!(TemplateModule::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 2 + )); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens cannot be created by non-privileged and non-white listed address. +#[test] +fn white_list_test_15() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + + assert_noop!(TemplateModule::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 2 + ), "Address is not in white list"); + }); +} + +// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens can be created by non-privileged and white listed address. +#[test] +fn white_list_test_16() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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(), + col_desc1.clone(), + token_prefix1.clone(), + mode + )); + + assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList)); + assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true)); + assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2)); + + assert_ok!(TemplateModule::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 2 + )); + }); +} + +// #endregion \ No newline at end of file --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -53,8 +53,10 @@ pub use timestamp::Call as TimestampCall; -/// Importing a nft pallet -pub use nft; +/// Re-export a nft pallet +/// TODO: Check this re-export. Is this safe and good style? +extern crate nft; +pub use nft::*; /// An index to a block. pub type BlockNumber = u32; @@ -318,7 +320,7 @@ Balances: balances::{Module, Call, Storage, Config, Event}, TransactionPayment: transaction_payment::{Module, Storage}, Sudo: sudo::{Module, Call, Config, Storage, Event}, - Nft: nft::{Module, Call, Storage, Event}, + Nft: nft::{Module, Call, Config, Storage, Event}, } );