From 8eb82cd98ff640e158639ddcd688b7c56710aeba Mon Sep 17 00:00:00 2001 From: sotmorskiy Date: Mon, 05 Oct 2020 07:15:18 +0000 Subject: [PATCH] Merge branch 'develop' into feature/NFTPAR-93 # Conflicts: # tests/package-lock.json # tests/package.json --- --- 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,19 +187,25 @@ "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" }, + "ApprovePermissions": { + "Approved": "AccountId", + "Amount": "u64" + }, "RawData": "Vec", "Address": "AccountId", "LookupSource": "AccountId", "Weight": "u64" } + ``` \ No newline at end of file --- 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, @@ -97,51 +102,52 @@ 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 } -#[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, @@ -155,6 +161,8 @@ type Event: From> + Into<::Event>; } +// #endregion + decl_storage! { trait Store for Module as Nft { @@ -164,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; @@ -175,12 +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; - - // Active vesting list - // pub VestingList get(fn vesting): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => VestingItem; + 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; @@ -189,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!( @@ -209,7 +234,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 +249,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 +296,7 @@ owner: who.clone(), name: name, mode: mode.clone(), + mint_mode: false, access: AccessMode::Normal, description: description, decimal_points: decimal_points, @@ -309,6 +335,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 +498,15 @@ 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, "Public minting is not allowed for this collection"); + Self::check_white_list(collection_id, &owner)?; + Self::check_white_list(collection_id, &sender)?; + } + match target_collection.mode { CollectionMode::NFT(_) => { @@ -469,12 +568,17 @@ pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult { let sender = ensure_signed(origin)?; - 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())?; - } + Self::collection_exists(collection_id)?; + + // 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)?; + } match target_collection.mode { @@ -494,11 +598,18 @@ 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"); + // 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)?; + Self::check_white_list(collection_id, &recipient)?; + } - // TODO: implement other modes match target_collection.mode { CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?, @@ -515,10 +626,19 @@ let sender = ensure_signed(origin)?; + // 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"); + + if target_collection.access == AccessMode::WhiteList { + Self::check_white_list(collection_id, &sender)?; + Self::check_white_list(collection_id, &approved)?; + } + // 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 list_exists = >::contains_key(collection_id, (item_id, sender.clone())); if list_exists { @@ -544,26 +664,32 @@ 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())); - if approved_list_exists - { + let mut appoved_transfer = false; + + // 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()); - ensure!(opt_item.is_some(), "No approve found"); + appoved_transfer = opt_item.is_some(); 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)?; } + // 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"); + + if target_collection.access == AccessMode::WhiteList { + Self::check_white_list(collection_id, &sender)?; + Self::check_white_list(collection_id, &recipient)?; + } + // 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); + + match target_collection.mode { CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?, @@ -675,11 +801,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 +892,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 +936,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), mes); + + Ok(()) + } + fn transfer_fungible( collection_id: u64, item_id: u64, @@ -819,6 +953,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 +1043,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 +1129,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!( @@ -1014,6 +1166,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 { @@ -1068,6 +1294,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// // Economic models +// #region /// Fee multiplier. pub type Multiplier = FixedU128; @@ -1262,3 +1489,4 @@ Ok(()) } } +// #endregion \ No newline at end of file --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -1,8 +1,10 @@ // 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}; +// Use cases tests region +// #region #[test] fn create_nft_item() { new_test_ext().execute_with(|| { @@ -321,15 +323,16 @@ 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)); 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], @@ -390,15 +393,16 @@ 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)); 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], @@ -425,7 +429,7 @@ assert_eq!( TemplateModule::approved(1, (1, 1))[0], ApprovePermissions { - approved: 10, + approved: 3, amount: 100000000 } ); @@ -461,15 +465,16 @@ 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)); 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], @@ -496,7 +501,7 @@ assert_eq!( TemplateModule::approved(1, (1, 1))[0], ApprovePermissions { - approved: 10, + approved: 3, amount: 100000000 } ); @@ -573,7 +578,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 +587,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 +618,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 +627,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 +664,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 +936,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, @@ -942,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/run-testnet.sh +++ b/run-testnet.sh @@ -49,6 +49,7 @@ --rpc-port $RPCPORT \ --name $NODE \ --ws-external \ + --ws-max-connections 10000 \ --rpc-cors all \ -lruntime \ $BOOTNODES; --- 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; @@ -111,7 +113,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, @@ -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}, } ); --- /dev/null +++ b/tests/src/blocks-production.test.ts @@ -0,0 +1,48 @@ +import usingApi from "./substrate/substrate-api"; +import promisifySubstrate from "./substrate/promisify-substrate"; +import { expect } from "chai"; + +describe('Blocks Production', () => { + it('Node produces new blocks', async () => { + await usingApi(async api => { + const blocksPromise = promisifySubstrate(api, () => { + return new Promise((resolve, reject) => { + const blockNumbers: number[] = []; + const unsubscribe = api.rpc.chain.subscribeNewHeads(async head => { + blockNumbers.push(head.number.toNumber()); + if(blockNumbers.length >= 2) { + (await unsubscribe)(); + resolve(blockNumbers); + } + }); + }) + })(); + + let blocks: number[] | undefined = undefined; + + const timeoutPromise = new Promise((resolve, reject) => { + let secondsPassed = 0; + let incrementSeconds = () => { + secondsPassed++; + if(secondsPassed > 5 * 60) { + reject('Block production test failed due to timeout.'); + return; + } + + if(blocks) { + resolve(); + return; + } + + setTimeout(incrementSeconds, 1000); + } + + incrementSeconds(); + }); + + blocks = await Promise.race([blocksPromise, timeoutPromise]) as number[]; + + expect(blocks[0]).to.be.lessThan(blocks[1]); + }); + }); +}); --- a/tests/src/config.ts +++ b/tests/src/config.ts @@ -34,6 +34,20 @@ "ReFungible": "(u32, u32)" } }, + "Ownership": { + "Owner": "AccountId", + "Fraction": "u128" + }, + "FungibleItemType": { + "Collection": "u64", + "Owner": "AccountId", + "Value": "u128" + }, + "ReFungibleItemType": { + "Collection": "u64", + "Owner": "Vec", + "Data": "Vec" + }, "NftItemType": { "Collection": "u64", "Owner": "AccountId", @@ -57,10 +71,15 @@ "Description": "Vec", "TokenPrefix": "Vec", "CustomDataSize": "u32", + "MintMode": "bool", "OffchainSchema": "Vec", "Sponsor": "AccountId", "UnconfirmedSponsor": "AccountId" }, + "ApprovePermissions": { + "Approved": "AccountId", + "Amount": "u64" + }, "RawData": "Vec", "Address": "AccountId", "LookupSource": "AccountId", --- a/tests/src/connection.test.ts +++ b/tests/src/connection.test.ts @@ -15,9 +15,9 @@ }); }); - it('Cannot connect to 0.0.0.0', () => { - const neverConnectProvider = new WsProvider('ws://0.0.0.0:9944'); - expect((async () => { + it('Cannot connect to 255.255.255.255', async () => { + const neverConnectProvider = new WsProvider('ws://255.255.255.255:9944'); + await expect((async () => { await usingApi(async api => { const health = await api.rpc.system.health(); }, { provider: neverConnectProvider }); --- a/tests/src/substrate/promisify-substrate.ts +++ b/tests/src/substrate/promisify-substrate.ts @@ -4,19 +4,21 @@ export default function promisifySubstrate any>(api: ApiPromise, action: T): (...args: Parameters) => Promise>> { return (...args: Parameters) => { - const promise = new Promise>>((resolve, reject) => { + const promise = new Promise>>((resolve: ((result: PromiseType>) => void) | undefined, reject: ((error: any) => void) | undefined) => { const cleanup = () => { api.off('disconnected', fail); api.off('error', fail); + resolve = undefined; + reject = undefined; }; const success = (r: any) => { + resolve && resolve(r); cleanup(); - resolve(r); }; const fail = (error: any) => { + reject && reject(error); cleanup(); - reject(error); }; api.on('disconnected', fail); --- a/tests/src/substrate/substrate-api.ts +++ b/tests/src/substrate/substrate-api.ts @@ -10,13 +10,16 @@ export default async function usingApi(action: (api: ApiPromise) => Promise, settings: ApiOptions | undefined = undefined): Promise { settings = settings || defaultApiOptions(); - let api: ApiPromise | undefined = undefined; + let api: ApiPromise = new ApiPromise(settings); try { - api = new ApiPromise(settings); - await promisifySubstrate(api, () => api && api.isReady)(); - await action(api); + await promisifySubstrate(api, async () => { + if(api) { + await api.isReadyOrError; + await action(api); + } + })(); } finally { - api && api.disconnect(); + await api.disconnect(); } } \ No newline at end of file -- gitstuff