git.delta.rocks / unique-network / refs/commits / 8eb82cd98ff6

difftreelog

Merge branch 'develop' into feature/NFTPAR-93

sotmorskiy2020-10-05parents: #4fc8a38 #ab1a681.patch.diff
in: master
# Conflicts:
#	tests/package-lock.json
#	tests/package.json

11 files changed

modifiedREADME.mddiffbeforeafterboth
--- 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<u16>",
     "Description": "Vec<u16>",
     "TokenPrefix": "Vec<u8>",
     "CustomDataSize": "u32",
+    "MintMode": "bool",
     "OffchainSchema": "Vec<u8>",
     "Sponsor": "AccountId",
     "UnconfirmedSponsor": "AccountId"
   },
+  "ApprovePermissions": {
+    "Approved": "AccountId",
+    "Amount": "u64"
+  },
   "RawData": "Vec<u8>",
   "Address": "AccountId",
   "LookupSource": "AccountId",
   "Weight": "u64"
 }
+
 ```
\ No newline at end of file
modifiednode/src/chain_spec.rsdiffbeforeafterboth
--- 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::<sr25519::Public>("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::<sr25519::Public>("Alice"),
+                unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+            })],
+            nft_item_id: vec!(),
+            fungible_item_id: vec!(),
+            refungible_item_id: vec!(),
+        }),
         contracts: Some(ContractsConfig {
             current_schedule: ContractsSchedule {
                 enable_println,
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
1#![cfg_attr(not(feature = "std"), no_std)]1#![cfg_attr(not(feature = "std"), no_std)]
22
3/// For more guidance on Substrate FRAME, see the example pallet3#[cfg(feature = "std")]
4/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs4pub use serde::*;
5
5use codec::{Decode, Encode};6use codec::{Decode, Encode};
6pub use frame_support::{7pub use frame_support::{
32 },33 },
33 FixedPointOperand, FixedU128,34 FixedPointOperand, FixedU128,
34};35};
35use sp_std::prelude::*;
3636
37#[cfg(test)]37#[cfg(test)]
38mod mock;38mod mock;
3939
40#[cfg(test)]40#[cfg(test)]
41mod tests;41mod tests;
4242
43// Structs
43#[derive(Encode, Decode, Debug, Eq, Clone, PartialEq)]44// #region
45
46#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
47#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
44pub enum CollectionMode {48pub enum CollectionMode {
45 Invalid,49 Invalid,
46 // custom data size50 // custom data size
62 }66 }
63}67}
6468
65#[derive(Encode, Decode, Debug, Clone, PartialEq)]69#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
70#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
66pub enum AccessMode {71pub enum AccessMode {
67 Normal,72 Normal,
68 WhiteList,73 WhiteList,
79 }84 }
80}85}
8186
82#[derive(Encode, Decode, Default, Clone, PartialEq)]87#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
83#[cfg_attr(feature = "std", derive(Debug))]88#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
84pub struct Ownership<AccountId> {89pub struct Ownership<AccountId> {
85 pub owner: AccountId,90 pub owner: AccountId,
86 pub fraction: u128,91 pub fraction: u128,
87}92}
8893
89#[derive(Encode, Decode, Default, Clone, PartialEq)]94#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
90#[cfg_attr(feature = "std", derive(Debug))]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
91pub struct CollectionType<AccountId> {96pub struct CollectionType<AccountId> {
92 pub owner: AccountId,97 pub owner: AccountId,
93 pub mode: CollectionMode,98 pub mode: CollectionMode,
97 pub description: Vec<u16>, // 256 include null escape char102 pub description: Vec<u16>, // 256 include null escape char
98 pub token_prefix: Vec<u8>, // 16 include null escape char103 pub token_prefix: Vec<u8>, // 16 include null escape char
99 pub custom_data_size: u32,104 pub custom_data_size: u32,
105 pub mint_mode: bool,
100 pub offchain_schema: Vec<u8>,106 pub offchain_schema: Vec<u8>,
101 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender107 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender
102 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship108 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship
103}109}
104110
105#[derive(Encode, Decode, Default, Clone, PartialEq)]111#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
106#[cfg_attr(feature = "std", derive(Debug))]112#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
107pub struct CollectionAdminsType<AccountId> {113pub struct CollectionAdminsType<AccountId> {
108 pub admin: AccountId,114 pub admin: AccountId,
109 pub collection_id: u64,115 pub collection_id: u64,
110}116}
111117
112#[derive(Encode, Decode, Default, Clone, PartialEq)]118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
113#[cfg_attr(feature = "std", derive(Debug))]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
114pub struct NftItemType<AccountId> {120pub struct NftItemType<AccountId> {
115 pub collection: u64,121 pub collection: u64,
116 pub owner: AccountId,122 pub owner: AccountId,
117 pub data: Vec<u8>,123 pub data: Vec<u8>,
118}124}
119125
120#[derive(Encode, Decode, Default, Clone, PartialEq)]126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
121#[cfg_attr(feature = "std", derive(Debug))]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
122pub struct FungibleItemType<AccountId> {128pub struct FungibleItemType<AccountId> {
123 pub collection: u64,129 pub collection: u64,
124 pub owner: AccountId,130 pub owner: AccountId,
125 pub value: u128,131 pub value: u128,
126}132}
127133
128#[derive(Encode, Decode, Default, Clone, PartialEq)]134#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
129#[cfg_attr(feature = "std", derive(Debug))]135#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
130pub struct ReFungibleItemType<AccountId> {136pub struct ReFungibleItemType<AccountId> {
131 pub collection: u64,137 pub collection: u64,
132 pub owner: Vec<Ownership<AccountId>>,138 pub owner: Vec<Ownership<AccountId>>,
133 pub data: Vec<u8>,139 pub data: Vec<u8>,
134}140}
135141
136#[derive(Encode, Decode, Default, Clone, PartialEq)]142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
137#[cfg_attr(feature = "std", derive(Debug))]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
138pub struct ApprovePermissions<AccountId> {144pub struct ApprovePermissions<AccountId> {
139 pub approved: AccountId,145 pub approved: AccountId,
140 pub amount: u64,146 pub amount: u64,
141}147}
142148
143#[derive(Encode, Decode, Default, Clone, PartialEq)]149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
144#[cfg_attr(feature = "std", derive(Debug))]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
145pub struct VestingItem<AccountId, Moment> {151pub struct VestingItem<AccountId, Moment> {
146 pub sender: AccountId,152 pub sender: AccountId,
147 pub recipient: AccountId,153 pub recipient: AccountId,
155 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;161 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
156}162}
157163
164// #endregion
165
158decl_storage! {166decl_storage! {
159 trait Store for Module<T: Trait> as Nft {167 trait Store for Module<T: Trait> as Nft {
160168
164 ChainVersion: u64;172 ChainVersion: u64;
165 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;173 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;
166174
167 pub Collection get(fn collection): map hasher(identity) u64 => CollectionType<T::AccountId>;175 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;
168 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;176 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;
169 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;177 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;
170178
175 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;183 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;
176184
177 /// Item collections185 /// Item collections
178 pub NftItemList get(fn nft_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;186 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;
179 pub FungibleItemList get(fn fungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;187 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;
180 pub ReFungibleItemList get(fn refungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;188 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;
181189
182 // Active vesting list
183 // pub VestingList get(fn vesting): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => VestingItem<T::AccountId, T::Moment>;
184
185 /// Index list190 /// Index list
186 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;191 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;
187192
188 // Sponsorship193 // Sponsorship
189 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;194 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;
190 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;195 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;
191 }196 }
197 add_extra_genesis {
198 build(|config: &GenesisConfig<T>| {
199 // Modification of storage
200 for (_num, _c) in &config.collection {
201 <Module<T>>::init_collection(_c);
202 }
203
204 for (_num, _q, _i) in &config.nft_item_id {
205 <Module<T>>::init_nft_token(_i);
206 }
207
208 for (_num, _q, _i) in &config.fungible_item_id {
209 <Module<T>>::init_fungible_token(_i);
210 }
211
212 for (_num, _q, _i) in &config.refungible_item_id {
213 <Module<T>>::init_refungible_token(_i);
214 }
215 })
216 }
192}217}
193218
194decl_event!(219decl_event!(
209234
210 fn on_initialize(now: T::BlockNumber) -> Weight {235 fn on_initialize(now: T::BlockNumber) -> Weight {
211236
212 if ChainVersion::get() == 0237 if ChainVersion::get() < 2
213 {238 {
214 let value = NextCollectionID::get();239 let value = NextCollectionID::get();
215 CreatedCollectionCount::put(value);240 CreatedCollectionCount::put(value);
224 // @param customDataSz size of custom data in each collection item249 // @param customDataSz size of custom data in each collection item
225 // returns collection ID250 // returns collection ID
226 #[weight = 0]251 #[weight = 0]
227 pub fn create_collection( origin,252 pub fn create_collection(origin,
228 collection_name: Vec<u16>,253 collection_name: Vec<u16>,
229 collection_description: Vec<u16>,254 collection_description: Vec<u16>,
230 token_prefix: Vec<u8>,255 token_prefix: Vec<u8>,
231 mode: CollectionMode) -> DispatchResult {256 mode: CollectionMode) -> DispatchResult {
232257
233 // Anyone can create a collection258 // Anyone can create a collection
234 let who = ensure_signed(origin)?;259 let who = ensure_signed(origin)?;
271 owner: who.clone(),296 owner: who.clone(),
272 name: name,297 name: name,
273 mode: mode.clone(),298 mode: mode.clone(),
299 mint_mode: false,
274 access: AccessMode::Normal,300 access: AccessMode::Normal,
275 description: description,301 description: description,
276 decimal_points: decimal_points,302 decimal_points: decimal_points,
309 }335 }
310336
311 #[weight = 0]337 #[weight = 0]
338 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{
339
340 let sender = ensure_signed(origin)?;
341 Self::check_owner_or_admin_permissions(collection_id, sender)?;
342
343 let mut white_list_collection: Vec<T::AccountId>;
344 if <WhiteList<T>>::contains_key(collection_id) {
345 white_list_collection = <WhiteList<T>>::get(collection_id);
346 if !white_list_collection.contains(&address.clone())
347 {
348 white_list_collection.push(address.clone());
349 }
350 }
351 else {
352 white_list_collection = Vec::new();
353 white_list_collection.push(address.clone());
354 }
355
356 <WhiteList<T>>::insert(collection_id, white_list_collection);
357 Ok(())
358 }
359
360 #[weight = 0]
361 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{
362
363 let sender = ensure_signed(origin)?;
364 Self::check_owner_or_admin_permissions(collection_id, sender)?;
365
366 if <WhiteList<T>>::contains_key(collection_id) {
367 let mut white_list_collection = <WhiteList<T>>::get(collection_id);
368 if white_list_collection.contains(&address.clone())
369 {
370 white_list_collection.retain(|i| *i != address.clone());
371 <WhiteList<T>>::insert(collection_id, white_list_collection);
372 }
373 }
374
375 Ok(())
376 }
377
378 #[weight = 0]
379 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult
380 {
381 let sender = ensure_signed(origin)?;
382
383 Self::check_owner_permissions(collection_id, sender)?;
384 let mut target_collection = <Collection<T>>::get(collection_id);
385 target_collection.access = mode;
386 <Collection<T>>::insert(collection_id, target_collection);
387
388 Ok(())
389 }
390
391 #[weight = 0]
392 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult
393 {
394 let sender = ensure_signed(origin)?;
395
396 Self::check_owner_permissions(collection_id, sender)?;
397 let mut target_collection = <Collection<T>>::get(collection_id);
398 target_collection.mint_mode = mint_permission;
399 <Collection<T>>::insert(collection_id, target_collection);
400
401 Ok(())
402 }
403
404 #[weight = 0]
312 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {405 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {
313406
314 let sender = ensure_signed(origin)?;407 let sender = ensure_signed(origin)?;
405 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {498 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {
406499
407 let sender = ensure_signed(origin)?;500 let sender = ensure_signed(origin)?;
501 Self::collection_exists(collection_id)?;
408 let target_collection = <Collection<T>>::get(collection_id);502 let target_collection = <Collection<T>>::get(collection_id);
409 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;
410503
504 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {
505 ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection");
506 Self::check_white_list(collection_id, &owner)?;
507 Self::check_white_list(collection_id, &sender)?;
508 }
509
411 match target_collection.mode510 match target_collection.mode
412 {511 {
413 CollectionMode::NFT(_) => {512 CollectionMode::NFT(_) => {
469 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {568 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {
470569
471 let sender = ensure_signed(origin)?;570 let sender = ensure_signed(origin)?;
472 let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id);571 Self::collection_exists(collection_id)?;
473 if !item_owner572
474 {573 // Transfer permissions check
475 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;
476 }
477 let target_collection = <Collection<T>>::get(collection_id);574 let target_collection = <Collection<T>>::get(collection_id);
575 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||
576 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
577 "Only item owner, collection owner and admins can modify item");
478578
579 if target_collection.access == AccessMode::WhiteList {
580 Self::check_white_list(collection_id, &sender)?;
581 }
582
479 match target_collection.mode583 match target_collection.mode
480 {584 {
481 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,585 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,
494 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {598 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {
495599
496 let sender = ensure_signed(origin)?;600 let sender = ensure_signed(origin)?;
497 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id), "Only item owner can call transfer method");
498601
602 // Transfer permissions check
499 let target_collection = <Collection<T>>::get(collection_id);603 let target_collection = <Collection<T>>::get(collection_id);
604 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||
605 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
606 "Only item owner, collection owner and admins can modify item");
500607
501 // TODO: implement other modes608 if target_collection.access == AccessMode::WhiteList {
609 Self::check_white_list(collection_id, &sender)?;
610 Self::check_white_list(collection_id, &recipient)?;
611 }
612
502 match target_collection.mode613 match target_collection.mode
503 {614 {
515626
516 let sender = ensure_signed(origin)?;627 let sender = ensure_signed(origin)?;
517628
629 // Transfer permissions check
630 let target_collection = <Collection<T>>::get(collection_id);
631 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||
632 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
633 "Only item owner, collection owner and admins can approve");
634
635 if target_collection.access == AccessMode::WhiteList {
636 Self::check_white_list(collection_id, &sender)?;
637 Self::check_white_list(collection_id, &approved)?;
638 }
639
518 // amount param stub640 // amount param stub
519 let amount = 100000000;641 let amount = 100000000;
520642
521 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id), "Only item owner can call transfer method");
522
523 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));643 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));
524 if list_exists {644 if list_exists {
525645
544 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {664 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {
545665
546 let sender = ensure_signed(origin)?;666 let sender = ensure_signed(origin)?;
547 let approved_list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone()));667 let mut appoved_transfer = false;
668
548 if approved_list_exists669 // Check approve
549 {670 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {
550 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));671 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));
551 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());672 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());
552 ensure!(opt_item.is_some(), "No approve found");673 appoved_transfer = opt_item.is_some();
553 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");674 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");
554
555 // remove approve
556 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))
557 .into_iter().filter(|i| i.approved != sender.clone()).collect();
558 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);
559 }675 }
560 else
561 {
562 Self::check_owner_or_admin_permissions(collection_id, sender)?;
563 }
564676
677 // Transfer permissions check
565 let target_collection = <Collection<T>>::get(collection_id);678 let target_collection = <Collection<T>>::get(collection_id);
679 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
680 "Only item owner, collection owner and admins can modify items");
566681
682 if target_collection.access == AccessMode::WhiteList {
683 Self::check_white_list(collection_id, &sender)?;
684 Self::check_white_list(collection_id, &recipient)?;
685 }
686
687 // remove approve
688 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))
689 .into_iter().filter(|i| i.approved != sender.clone()).collect();
690 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);
691
692
567 match target_collection.mode693 match target_collection.mode
568 {694 {
569 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,695 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,
675 Ok(())801 Ok(())
676 }802 }
677803
678 fn burn_refungible_item(804 fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult {
679 collection_id: u64,
680 item_id: u64,
681 owner: T::AccountId,
682 ) -> DispatchResult {
683 ensure!(805 ensure!(
684 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),806 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),
685 "Item does not exists"807 "Item does not exists"
770 Ok(())892 Ok(())
771 }893 }
772894
773 fn check_owner_or_admin_permissions(895 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {
774 collection_id: u64,
775 subject: T::AccountId,
776 ) -> DispatchResult {
777 Self::collection_exists(collection_id)?;
778896
779 let target_collection = <Collection<T>>::get(collection_id);897 let target_collection = <Collection<T>>::get(collection_id);
780 let is_owner = subject == target_collection.owner;898 let mut result: bool = subject == target_collection.owner;
781
782 let no_perm_mes = "You do not have permissions to modify this collection";
783 let exists = <AdminList<T>>::contains_key(collection_id);899 let exists = <AdminList<T>>::contains_key(collection_id);
784900
785 if !is_owner {901 if !result & exists {
786 ensure!(exists, no_perm_mes);
787 ensure!(902 if <AdminList<T>>::get(collection_id).contains(&subject) {
788 <AdminList<T>>::get(collection_id).contains(&subject),
789 no_perm_mes903 result = true
790 );904 }
791 }905 }
906
907 result
908 }
909
910 fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {
911
912 Self::collection_exists(collection_id)?;
913 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());
914
915 ensure!(result, "You do not have permissions to modify this collection");
792 Ok(())916 Ok(())
793 }917 }
794918
812 }936 }
813 }937 }
814938
939 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {
940
941 let mes = "Address is not in white list";
942 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);
943 let wl = <WhiteList<T>>::get(collection_id);
944 ensure!(wl.contains(address), mes);
945
946 Ok(())
947 }
948
815 fn transfer_fungible(949 fn transfer_fungible(
816 collection_id: u64,950 collection_id: u64,
817 item_id: u64,951 item_id: u64,
818 value: u64,952 value: u64,
819 owner: T::AccountId,953 owner: T::AccountId,
820 new_owner: T::AccountId,954 new_owner: T::AccountId,
821 ) -> DispatchResult {955 ) -> DispatchResult {
956
957 ensure!(
958 <FungibleItemList<T>>::contains_key(collection_id, item_id),
959 "Item not exists"
960 );
961
822 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);962 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);
823 let amount = full_item.value;963 let amount = full_item.value;
824964
903 owner: T::AccountId,1043 owner: T::AccountId,
904 new_owner: T::AccountId,1044 new_owner: T::AccountId,
905 ) -> DispatchResult {1045 ) -> DispatchResult {
1046
1047 ensure!(
1048 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),
1049 "Item not exists"
1050 );
1051
906 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1052 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);
907 let item = full_item1053 let item = full_item
908 .owner1054 .owner
983 sender: T::AccountId,1129 sender: T::AccountId,
984 new_owner: T::AccountId,1130 new_owner: T::AccountId,
985 ) -> DispatchResult {1131 ) -> DispatchResult {
1132
1133 ensure!(
1134 <NftItemList<T>>::contains_key(collection_id, item_id),
1135 "Item not exists"
1136 );
1137
986 let mut item = <NftItemList<T>>::get(collection_id, item_id);1138 let mut item = <NftItemList<T>>::get(collection_id, item_id);
9871139
988 ensure!(1140 ensure!(
1012 // reset approved list1164 // reset approved list
1013 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1165 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));
1014 Ok(())1166 Ok(())
1167 }
1168
1169 fn init_collection(item: &CollectionType<T::AccountId>){
1170
1171 // check params
1172 assert!(item.decimal_points <= 4, "decimal_points parameter must be lower than 4");
1173 assert!(item.name.len() <= 64, "Collection name can not be longer than 63 char");
1174 assert!(item.name.len() <= 256, "Collection description can not be longer than 255 char");
1175 assert!(item.token_prefix.len() <= 16, "Token prefix can not be longer than 15 char");
1176
1177 // Generate next collection ID
1178 let next_id = CreatedCollectionCount::get()
1179 .checked_add(1)
1180 .expect("collection id error");
1181
1182 CreatedCollectionCount::put(next_id);
1183 }
1184
1185 fn init_nft_token(item: &NftItemType<T::AccountId>){
1186
1187 let current_index = <ItemListIndex>::get(item.collection)
1188 .checked_add(1)
1189 .expect("Item list index id error");
1190
1191 let item_owner = item.owner.clone();
1192 let collection_id = item.collection.clone();
1193 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();
1194
1195 <ItemListIndex>::insert(collection_id, current_index);
1196
1197 // Update balance
1198 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())
1199 .checked_add(1)
1200 .unwrap();
1201 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);
1202 }
1203
1204 fn init_fungible_token(item: &FungibleItemType<T::AccountId>){
1205
1206 let current_index = <ItemListIndex>::get(item.collection)
1207 .checked_add(1)
1208 .expect("Item list index id error");
1209 let owner = item.owner.clone();
1210 let value = item.value as u64;
1211
1212 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();
1213
1214 <ItemListIndex>::insert(item.collection, current_index);
1215
1216 // Update balance
1217 let new_balance = <Balance<T>>::get(item.collection, owner.clone())
1218 .checked_add(value)
1219 .unwrap();
1220 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);
1221 }
1222
1223 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>){
1224
1225 let current_index = <ItemListIndex>::get(item.collection)
1226 .checked_add(1)
1227 .expect("Item list index id error");
1228
1229 let value = item.owner.first().unwrap().fraction as u64;
1230 let owner = item.owner.first().unwrap().owner.clone();
1231
1232 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();
1233
1234 <ItemListIndex>::insert(item.collection, current_index);
1235
1236 // Update balance
1237 let new_balance = <Balance<T>>::get(item.collection, owner.clone())
1238 .checked_add(value)
1239 .unwrap();
1240 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);
1015 }1241 }
10161242
1017 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {1243 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {
10681294
1069////////////////////////////////////////////////////////////////////////////////////////////////////1295////////////////////////////////////////////////////////////////////////////////////////////////////
1070// Economic models1296// Economic models
1297// #region
10711298
1072/// Fee multiplier.1299/// Fee multiplier.
1073pub type Multiplier = FixedU128;1300pub type Multiplier = FixedU128;
1262 Ok(())1489 Ok(())
1263 }1490 }
1264}1491}
12651492// #endregion
modifiedpallets/nft/src/tests.rsdiffbeforeafterboth
--- 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+        let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+        let token_prefix1: Vec<u8> = 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
modifiedrun-testnet.shdiffbeforeafterboth
--- 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;
modifiedruntime/src/lib.rsdiffbeforeafterboth
--- 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<T>, Event<T>},
         TransactionPayment: transaction_payment::{Module, Storage},
         Sudo: sudo::{Module, Call, Config<T>, Storage, Event<T>},
-        Nft: nft::{Module, Call, Storage, Event<T>},
+        Nft: nft::{Module, Call, Config<T>, Storage, Event<T>},
     }
 );
 
addedtests/src/blocks-production.test.tsdiffbeforeafterboth
--- /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<number[]>((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<void>((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]);
+    });
+  });
+});
modifiedtests/src/config.tsdiffbeforeafterboth
--- 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<Ownership>",
+      "Data": "Vec<u8>"
+    },
     "NftItemType": {
       "Collection": "u64",
       "Owner": "AccountId",
@@ -57,10 +71,15 @@
       "Description": "Vec<u16>",
       "TokenPrefix": "Vec<u8>",
       "CustomDataSize": "u32",
+      "MintMode": "bool",
       "OffchainSchema": "Vec<u8>",
       "Sponsor": "AccountId",
       "UnconfirmedSponsor": "AccountId"
     },
+    "ApprovePermissions": {
+      "Approved": "AccountId",
+      "Amount": "u64"
+    },
     "RawData": "Vec<u8>",
     "Address": "AccountId",
     "LookupSource": "AccountId",
modifiedtests/src/connection.test.tsdiffbeforeafterboth
--- 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 });
modifiedtests/src/substrate/promisify-substrate.tsdiffbeforeafterboth
--- a/tests/src/substrate/promisify-substrate.ts
+++ b/tests/src/substrate/promisify-substrate.ts
@@ -4,19 +4,21 @@
 
 export default function promisifySubstrate<T extends (...args: any[]) => any>(api: ApiPromise, action: T): (...args: Parameters<T>) => Promise<PromiseType<ReturnType<T>>> {
   return (...args: Parameters<T>) => {
-    const promise = new Promise<PromiseType<ReturnType<T>>>((resolve, reject) => {
+    const promise = new Promise<PromiseType<ReturnType<T>>>((resolve: ((result: PromiseType<ReturnType<T>>) => 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);
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- 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<void>, settings: ApiOptions | undefined = undefined): Promise<void> {
   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