difftreelog
White List Unit Test NFT-104
in: master
2 files changed
pallets/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use serde::*;56use codec::{Decode, Encode};7pub use frame_support::{8 construct_runtime, decl_event, decl_module, decl_storage,9 dispatch::DispatchResult,10 ensure, parameter_types,11 traits::{12 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,13 Randomness, WithdrawReason,14 },15 weights::{16 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},17 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,18 WeightToFeePolynomial,19 },20 IsSubType, StorageValue,21};2223use frame_system::{self as system, ensure_signed};24use sp_runtime::sp_std::prelude::Vec;25use sp_runtime::{26 traits::{27 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,28 SignedExtension, Zero,29 },30 transaction_validity::{31 InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,32 ValidTransaction,33 },34 FixedPointOperand, FixedU128,35};3637#[cfg(test)]38mod mock;3940#[cfg(test)]41mod tests;4243#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]44#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]45pub enum CollectionMode {46 Invalid,47 // custom data size48 NFT(u32),49 // decimal points50 Fungible(u32),51 // custom data size and decimal points52 ReFungible(u32, u32),53}5455impl Into<u8> for CollectionMode {56 fn into(self) -> u8 {57 match self {58 CollectionMode::Invalid => 0,59 CollectionMode::NFT(_) => 1,60 CollectionMode::Fungible(_) => 2,61 CollectionMode::ReFungible(_, _) => 3,62 }63 }64}6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum AccessMode {69 Normal,70 WhiteList,71}72impl Default for AccessMode {73 fn default() -> Self {74 Self::Normal75 }76}7778impl Default for CollectionMode {79 fn default() -> Self {80 Self::Invalid81 }82}8384#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]85#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]86pub struct Ownership<AccountId> {87 pub owner: AccountId,88 pub fraction: u128,89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct CollectionType<AccountId> {94 pub owner: AccountId,95 pub mode: CollectionMode,96 pub access: AccessMode,97 pub decimal_points: u32,98 pub name: Vec<u16>, // 64 include null escape char99 pub description: Vec<u16>, // 256 include null escape char100 pub token_prefix: Vec<u8>, // 16 include null escape char101 pub custom_data_size: u32,102 pub mint_mode: bool,103 pub offchain_schema: Vec<u8>,104 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender105 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship106}107108#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]109#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]110pub struct CollectionAdminsType<AccountId> {111 pub admin: AccountId,112 pub collection_id: u64,113}114115#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]116#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]117pub struct NftItemType<AccountId> {118 pub collection: u64,119 pub owner: AccountId,120 pub data: Vec<u8>,121}122123#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]124#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]125pub struct FungibleItemType<AccountId> {126 pub collection: u64,127 pub owner: AccountId,128 pub value: u128,129}130131#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]132#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]133pub struct ReFungibleItemType<AccountId> {134 pub collection: u64,135 pub owner: Vec<Ownership<AccountId>>,136 pub data: Vec<u8>,137}138139#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]140#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]141pub struct ApprovePermissions<AccountId> {142 pub approved: AccountId,143 pub amount: u64,144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct VestingItem<AccountId, Moment> {149 pub sender: AccountId,150 pub recipient: AccountId,151 pub collection_id: u64,152 pub item_id: u64,153 pub amount: u64,154 pub vesting_date: Moment,155}156157pub trait Trait: system::Trait {158 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;159}160161decl_storage! {162 trait Store for Module<T: Trait> as Nft {163164 // Private members165 NextCollectionID: u64;166 CreatedCollectionCount: u64;167 ChainVersion: u64;168 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;169170 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;171 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;172 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;173174 /// Balance owner per collection map175 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;176177 /// second parameter: item id + owner account id178 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;179180 /// Item collections181 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;182 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;183 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;184185 /// Index list186 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;187188 // Sponsorship189 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;191 }192 add_extra_genesis {193 build(|config: &GenesisConfig<T>| {194 // Modification of storage195 for (_num, _c) in &config.collection {196 <Module<T>>::init_collection(_c);197 }198199 for (_num, _q, _i) in &config.nft_item_id {200 <Module<T>>::init_nft_token(_i);201 }202203 for (_num, _q, _i) in &config.fungible_item_id {204 <Module<T>>::init_fungible_token(_i);205 }206207 for (_num, _q, _i) in &config.refungible_item_id {208 <Module<T>>::init_refungible_token(_i);209 }210 })211 }212}213214decl_event!(215 pub enum Event<T>216 where217 AccountId = <T as system::Trait>::AccountId,218 {219 Created(u64, u8, AccountId),220 ItemCreated(u64, u64),221 ItemDestroyed(u64, u64),222 }223);224225decl_module! {226 pub struct Module<T: Trait> for enum Call where origin: T::Origin {227228 fn deposit_event() = default;229230 fn on_initialize(now: T::BlockNumber) -> Weight {231232 if ChainVersion::get() < 2233 {234 let value = NextCollectionID::get();235 CreatedCollectionCount::put(value);236 ChainVersion::put(2);237 }238239 0240 }241242 // Create collection of NFT with given parameters243 //244 // @param customDataSz size of custom data in each collection item245 // returns collection ID246 #[weight = 0]247 pub fn create_collection(origin,248 collection_name: Vec<u16>,249 collection_description: Vec<u16>,250 token_prefix: Vec<u8>,251 mode: CollectionMode) -> DispatchResult {252253 // Anyone can create a collection254 let who = ensure_signed(origin)?;255 let custom_data_size = match mode {256 CollectionMode::NFT(size) => size,257 CollectionMode::ReFungible(size, _) => size,258 _ => 0259 };260261 let decimal_points = match mode {262 CollectionMode::Fungible(points) => points,263 CollectionMode::ReFungible(_, points) => points,264 _ => 0265 };266267 // check params268 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");269270 let mut name = collection_name.to_vec();271 name.push(0);272 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");273274 let mut description = collection_description.to_vec();275 description.push(0);276 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");277278 let mut prefix = token_prefix.to_vec();279 prefix.push(0);280 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");281282 // Generate next collection ID283 let next_id = CreatedCollectionCount::get()284 .checked_add(1)285 .expect("collection id error");286287 CreatedCollectionCount::put(next_id);288289 // Create new collection290 let new_collection = CollectionType {291 owner: who.clone(),292 name: name,293 mode: mode.clone(),294 mint_mode: false,295 access: AccessMode::Normal,296 description: description,297 decimal_points: decimal_points,298 token_prefix: prefix,299 offchain_schema: Vec::new(),300 custom_data_size: custom_data_size,301 sponsor: T::AccountId::default(),302 unconfirmed_sponsor: T::AccountId::default(),303 };304305 // Add new collection to map306 <Collection<T>>::insert(next_id, new_collection);307308 // call event309 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));310311 Ok(())312 }313314 #[weight = 0]315 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {316317 let sender = ensure_signed(origin)?;318 Self::check_owner_permissions(collection_id, sender)?;319320 // TODO Items remove321 <AddressTokens<T>>::remove_prefix(collection_id);322 <ApprovedList<T>>::remove_prefix(collection_id);323 <Balance<T>>::remove_prefix(collection_id);324 <ItemListIndex>::remove(collection_id);325 <AdminList<T>>::remove(collection_id);326 <Collection<T>>::remove(collection_id);327 <WhiteList<T>>::remove(collection_id);328329 Ok(())330 }331332 #[weight = 0]333 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{334335 let sender = ensure_signed(origin)?;336 Self::check_owner_or_admin_permissions(collection_id, sender)?;337338 let mut white_list_collection: Vec<T::AccountId>;339 if <WhiteList<T>>::contains_key(collection_id) {340 white_list_collection = <WhiteList<T>>::get(collection_id);341 if !white_list_collection.contains(&address.clone())342 {343 white_list_collection.push(address.clone());344 }345 }346 else {347 white_list_collection = Vec::new();348 white_list_collection.push(address.clone());349 }350351 <WhiteList<T>>::insert(collection_id, white_list_collection);352 Ok(())353 }354355 #[weight = 0]356 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{357358 let sender = ensure_signed(origin)?;359 Self::check_owner_or_admin_permissions(collection_id, sender)?;360361 if <WhiteList<T>>::contains_key(collection_id) {362 let mut white_list_collection = <WhiteList<T>>::get(collection_id);363 if white_list_collection.contains(&address.clone())364 {365 white_list_collection.retain(|i| *i != address.clone());366 <WhiteList<T>>::insert(collection_id, white_list_collection);367 }368 }369370 Ok(())371 }372373 #[weight = 0]374 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult375 {376 let sender = ensure_signed(origin)?;377378 Self::check_owner_permissions(collection_id, sender)?;379 let mut target_collection = <Collection<T>>::get(collection_id);380 target_collection.access = mode;381 <Collection<T>>::insert(collection_id, target_collection);382383 Ok(())384 }385386 #[weight = 0]387 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult388 {389 let sender = ensure_signed(origin)?;390391 Self::check_owner_permissions(collection_id, sender)?;392 let mut target_collection = <Collection<T>>::get(collection_id);393 target_collection.mint_mode = mint_permission;394 <Collection<T>>::insert(collection_id, target_collection);395396 Ok(())397 }398399 #[weight = 0]400 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {401402 let sender = ensure_signed(origin)?;403 Self::check_owner_permissions(collection_id, sender)?;404 let mut target_collection = <Collection<T>>::get(collection_id);405 target_collection.owner = new_owner;406 <Collection<T>>::insert(collection_id, target_collection);407408 Ok(())409 }410411 #[weight = 0]412 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {413414 let sender = ensure_signed(origin)?;415 Self::check_owner_or_admin_permissions(collection_id, sender)?;416 let mut admin_arr: Vec<T::AccountId> = Vec::new();417418 if <AdminList<T>>::contains_key(collection_id)419 {420 admin_arr = <AdminList<T>>::get(collection_id);421 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");422 }423424 admin_arr.push(new_admin_id);425 <AdminList<T>>::insert(collection_id, admin_arr);426427 Ok(())428 }429430 #[weight = 0]431 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {432433 let sender = ensure_signed(origin)?;434 Self::check_owner_or_admin_permissions(collection_id, sender)?;435436 if <AdminList<T>>::contains_key(collection_id)437 {438 let mut admin_arr = <AdminList<T>>::get(collection_id);439 admin_arr.retain(|i| *i != account_id);440 <AdminList<T>>::insert(collection_id, admin_arr);441 }442443 Ok(())444 }445446 #[weight = 0]447 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {448449 let sender = ensure_signed(origin)?;450 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");451452 let mut target_collection = <Collection<T>>::get(collection_id);453 ensure!(sender == target_collection.owner, "You do not own this collection");454455 target_collection.unconfirmed_sponsor = new_sponsor;456 <Collection<T>>::insert(collection_id, target_collection);457458 Ok(())459 }460461 #[weight = 0]462 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {463464 let sender = ensure_signed(origin)?;465 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");466467 let mut target_collection = <Collection<T>>::get(collection_id);468 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");469470 target_collection.sponsor = target_collection.unconfirmed_sponsor;471 target_collection.unconfirmed_sponsor = T::AccountId::default();472 <Collection<T>>::insert(collection_id, target_collection);473474 Ok(())475 }476477 #[weight = 0]478 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {479480 let sender = ensure_signed(origin)?;481 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");482483 let mut target_collection = <Collection<T>>::get(collection_id);484 ensure!(sender == target_collection.owner, "You do not own this collection");485486 target_collection.sponsor = T::AccountId::default();487 <Collection<T>>::insert(collection_id, target_collection);488489 Ok(())490 }491492 #[weight = 0]493 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {494495 let sender = ensure_signed(origin)?;496 Self::collection_exists(collection_id)?;497 let target_collection = <Collection<T>>::get(collection_id);498499 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {500 ensure!(target_collection.mint_mode == true, "Collection is not in mint mode");501 Self::check_white_list(collection_id, owner.clone())?;502 }503504 match target_collection.mode505 {506 CollectionMode::NFT(_) => {507508 // check size509 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");510511 // Create nft item512 let item = NftItemType {513 collection: collection_id,514 owner: owner,515 data: properties,516 };517518 Self::add_nft_item(item)?;519520 },521 CollectionMode::Fungible(_) => {522523 // check size524 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");525526 let item = FungibleItemType {527 collection: collection_id,528 owner: owner,529 value: (10 as u128).pow(target_collection.decimal_points)530 };531532 Self::add_fungible_item(item)?;533 },534 CollectionMode::ReFungible(_, _) => {535536 // check size537 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");538539 let mut owner_list = Vec::new();540 let value = (10 as u128).pow(target_collection.decimal_points);541 owner_list.push(Ownership {owner: owner, fraction: value});542543 let item = ReFungibleItemType {544 collection: collection_id,545 owner: owner_list,546 data: properties547 };548549 Self::add_refungible_item(item)?;550 },551 _ => ()552 };553554 // call event555 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));556557 Ok(())558 }559560 #[weight = 0]561 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {562563 let sender = ensure_signed(origin)?;564 Self::collection_exists(collection_id)?;565566 // Transfer permissions check567 let target_collection = <Collection<T>>::get(collection_id);568 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 569 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 570 "Only item owner, collection owner and admins can modify item");571572 if target_collection.access == AccessMode::WhiteList {573 Self::check_white_list(collection_id, sender.clone())?;574 }575576 match target_collection.mode577 {578 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,579 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,580 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,581 _ => ()582 };583584 // call event585 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));586587 Ok(())588 }589590 #[weight = 0]591 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {592593 let sender = ensure_signed(origin)?;594595 // Transfer permissions check596 let target_collection = <Collection<T>>::get(collection_id);597 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 598 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 599 "Only item owner, collection owner and admins can modify item");600601 if target_collection.access == AccessMode::WhiteList {602 Self::check_white_list(collection_id, sender.clone())?;603 Self::check_white_list(collection_id, recipient.clone())?;604 }605606 match target_collection.mode607 {608 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,609 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,610 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,611 _ => ()612 };613614 Ok(())615 }616617 #[weight = 0]618 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {619620 let sender = ensure_signed(origin)?;621622 // Transfer permissions check623 let target_collection = <Collection<T>>::get(collection_id);624 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 625 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 626 "Only item owner, collection owner and admins can approve");627628 if target_collection.access == AccessMode::WhiteList {629 Self::check_white_list(collection_id, sender.clone())?;630 Self::check_white_list(collection_id, approved.clone())?;631 }632633 // amount param stub634 let amount = 100000000;635636 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));637 if list_exists {638639 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));640 let item_contains = list.iter().any(|i| i.approved == approved);641642 if !item_contains {643 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });644 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);645 }646 } else {647648 let mut list = Vec::new();649 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });650 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);651 }652653 Ok(())654 }655656 #[weight = 0]657 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {658659 let sender = ensure_signed(origin)?;660 let mut appoved_transfer = false;661662 // Check approve663 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {664 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));665 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());666 appoved_transfer = opt_item.is_some();667 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");668 }669670 // Transfer permissions check671 let target_collection = <Collection<T>>::get(collection_id);672 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 673 "Only item owner, collection owner and admins can modify items");674675 if target_collection.access == AccessMode::WhiteList {676 Self::check_white_list(collection_id, sender.clone())?;677 Self::check_white_list(collection_id, recipient.clone())?;678 }679680 // remove approve681 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))682 .into_iter().filter(|i| i.approved != sender.clone()).collect();683 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);684685686 match target_collection.mode687 {688 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,689 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,690 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,691 _ => ()692 };693694 Ok(())695 }696697 #[weight = 0]698 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {699700 // let no_perm_mes = "You do not have permissions to modify this collection";701 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);702 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));703 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);704705 // // on_nft_received call706707 // Self::transfer(origin, collection_id, item_id, new_owner)?;708709 Ok(())710 }711712 #[weight = 0]713 pub fn set_offchain_schema(714 origin,715 collection_id: u64,716 schema: Vec<u8>717 ) -> DispatchResult {718 let sender = ensure_signed(origin)?;719 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;720721 let mut target_collection = <Collection<T>>::get(collection_id);722 target_collection.offchain_schema = schema;723 <Collection<T>>::insert(collection_id, target_collection);724725 Ok(())726 }727 }728}729730impl<T: Trait> Module<T> {731 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {732 let current_index = <ItemListIndex>::get(item.collection)733 .checked_add(1)734 .expect("Item list index id error");735 let itemcopy = item.clone();736 let owner = item.owner.clone();737 let value = item.value as u64;738739 Self::add_token_index(item.collection, current_index, owner.clone())?;740741 <ItemListIndex>::insert(item.collection, current_index);742 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);743744 // Update balance745 let new_balance = <Balance<T>>::get(item.collection, owner.clone())746 .checked_add(value)747 .unwrap();748 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);749750 Ok(())751 }752753 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {754 let current_index = <ItemListIndex>::get(item.collection)755 .checked_add(1)756 .expect("Item list index id error");757 let itemcopy = item.clone();758759 let value = item.owner.first().unwrap().fraction as u64;760 let owner = item.owner.first().unwrap().owner.clone();761762 Self::add_token_index(item.collection, current_index, owner.clone())?;763764 <ItemListIndex>::insert(item.collection, current_index);765 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);766767 // Update balance768 let new_balance = <Balance<T>>::get(item.collection, owner.clone())769 .checked_add(value)770 .unwrap();771 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);772773 Ok(())774 }775776 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {777 let current_index = <ItemListIndex>::get(item.collection)778 .checked_add(1)779 .expect("Item list index id error");780781 let item_owner = item.owner.clone();782 let collection_id = item.collection.clone();783 Self::add_token_index(collection_id, current_index, item.owner.clone())?;784785 <ItemListIndex>::insert(collection_id, current_index);786 <NftItemList<T>>::insert(collection_id, current_index, item);787788 // Update balance789 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())790 .checked_add(1)791 .unwrap();792 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);793794 Ok(())795 }796797 fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult {798 ensure!(799 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),800 "Item does not exists"801 );802 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);803 let item = collection804 .owner805 .iter()806 .filter(|&i| i.owner == owner)807 .next()808 .unwrap();809 Self::remove_token_index(collection_id, item_id, owner.clone())?;810811 // remove approve list812 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));813814 // update balance815 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())816 .checked_sub(item.fraction as u64)817 .unwrap();818 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);819820 <ReFungibleItemList<T>>::remove(collection_id, item_id);821822 Ok(())823 }824825 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {826 ensure!(827 <NftItemList<T>>::contains_key(collection_id, item_id),828 "Item does not exists"829 );830 let item = <NftItemList<T>>::get(collection_id, item_id);831 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;832833 // remove approve list834 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));835836 // update balance837 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())838 .checked_sub(1)839 .unwrap();840 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);841 <NftItemList<T>>::remove(collection_id, item_id);842843 Ok(())844 }845846 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {847 ensure!(848 <FungibleItemList<T>>::contains_key(collection_id, item_id),849 "Item does not exists"850 );851 let item = <FungibleItemList<T>>::get(collection_id, item_id);852 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;853854 // remove approve list855 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));856857 // update balance858 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())859 .checked_sub(item.value as u64)860 .unwrap();861 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);862863 <FungibleItemList<T>>::remove(collection_id, item_id);864865 Ok(())866 }867868 fn collection_exists(collection_id: u64) -> DispatchResult {869 ensure!(870 <Collection<T>>::contains_key(collection_id),871 "This collection does not exist"872 );873 Ok(())874 }875876 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {877 Self::collection_exists(collection_id)?;878879 let target_collection = <Collection<T>>::get(collection_id);880 ensure!(881 subject == target_collection.owner,882 "You do not own this collection"883 );884885 Ok(())886 }887888 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {889890 let target_collection = <Collection<T>>::get(collection_id);891 let mut result: bool = subject == target_collection.owner;892 let exists = <AdminList<T>>::contains_key(collection_id);893894 if !result & exists {895 if <AdminList<T>>::get(collection_id).contains(&subject) {896 result = true897 }898 }899900 result901 }902903 fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {904 905 Self::collection_exists(collection_id)?;906 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());907908 ensure!(result, "You do not have permissions to modify this collection");909 Ok(())910 }911912 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {913 let target_collection = <Collection<T>>::get(collection_id);914915 match target_collection.mode {916 CollectionMode::NFT(_) => {917 <NftItemList<T>>::get(collection_id, item_id).owner == subject918 }919 CollectionMode::Fungible(_) => {920 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject921 }922 CollectionMode::ReFungible(_, _) => {923 <ReFungibleItemList<T>>::get(collection_id, item_id)924 .owner925 .iter()926 .any(|i| i.owner == subject)927 }928 CollectionMode::Invalid => false,929 }930 }931932 fn check_white_list(collection_id: u64, address: T::AccountId) -> DispatchResult {933934 let mes = "Address is not in white list";935 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);936 let wl = <WhiteList<T>>::get(collection_id);937 ensure!(wl.contains(&address.clone()), mes);938939 Ok(())940 }941942 fn transfer_fungible(943 collection_id: u64,944 item_id: u64,945 value: u64,946 owner: T::AccountId,947 new_owner: T::AccountId,948 ) -> DispatchResult {949950 ensure!(951 <FungibleItemList<T>>::contains_key(collection_id, item_id),952 "Item not exists"953 );954955 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);956 let amount = full_item.value;957958 ensure!(amount >= value.into(), "Item balance not enouth");959960 // update balance961 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())962 .checked_sub(value)963 .unwrap();964 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);965966 let mut new_owner_account_id = 0;967 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());968 if new_owner_items.len() > 0 {969 new_owner_account_id = new_owner_items[0];970 }971972 let val64 = value.into();973974 // transfer975 if amount == val64 && new_owner_account_id == 0 {976 // change owner977 // new owner do not have account978 let mut new_full_item = full_item.clone();979 new_full_item.owner = new_owner.clone();980 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);981982 // update balance983 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())984 .checked_add(value)985 .unwrap();986 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);987988 // update index collection989 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;990 } else {991 let mut new_full_item = full_item.clone();992 new_full_item.value -= val64;993994 // separate amount995 if new_owner_account_id > 0 {996 // new owner has account997 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);998 item.value += val64;9991000 // update balance1001 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1002 .checked_add(value)1003 .unwrap();1004 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10051006 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1007 } else {1008 // new owner do not have account1009 let item = FungibleItemType {1010 collection: collection_id,1011 owner: new_owner.clone(),1012 value: val64,1013 };10141015 Self::add_fungible_item(item)?;1016 }10171018 if amount == val64 {1019 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;10201021 // remove approve list1022 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1023 <FungibleItemList<T>>::remove(collection_id, item_id);1024 }10251026 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1027 }10281029 Ok(())1030 }10311032 fn transfer_refungible(1033 collection_id: u64,1034 item_id: u64,1035 value: u64,1036 owner: T::AccountId,1037 new_owner: T::AccountId,1038 ) -> DispatchResult {10391040 ensure!(1041 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1042 "Item not exists"1043 );10441045 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1046 let item = full_item1047 .owner1048 .iter()1049 .filter(|i| i.owner == owner)1050 .next()1051 .unwrap();1052 let amount = item.fraction;10531054 ensure!(amount >= value.into(), "Item balance not enouth");10551056 // update balance1057 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1058 .checked_sub(value)1059 .unwrap();1060 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);10611062 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1063 .checked_add(value)1064 .unwrap();1065 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10661067 let old_owner = item.owner.clone();1068 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1069 let val64 = value.into();10701071 // transfer1072 if amount == val64 && !new_owner_has_account {1073 // change owner1074 // new owner do not have account1075 let mut new_full_item = full_item.clone();1076 new_full_item1077 .owner1078 .iter_mut()1079 .find(|i| i.owner == owner)1080 .unwrap()1081 .owner = new_owner.clone();1082 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);10831084 // update index collection1085 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1086 } else {1087 let mut new_full_item = full_item.clone();1088 new_full_item1089 .owner1090 .iter_mut()1091 .find(|i| i.owner == owner)1092 .unwrap()1093 .fraction -= val64;10941095 // separate amount1096 if new_owner_has_account {1097 // new owner has account1098 new_full_item1099 .owner1100 .iter_mut()1101 .find(|i| i.owner == new_owner)1102 .unwrap()1103 .fraction += val64;1104 } else {1105 // new owner do not have account1106 new_full_item.owner.push(Ownership {1107 owner: new_owner.clone(),1108 fraction: val64,1109 });1110 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1111 }11121113 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1114 }11151116 Ok(())1117 }11181119 fn transfer_nft(1120 collection_id: u64,1121 item_id: u64,1122 sender: T::AccountId,1123 new_owner: T::AccountId,1124 ) -> DispatchResult {1125 1126 ensure!(1127 <NftItemList<T>>::contains_key(collection_id, item_id),1128 "Item not exists"1129 );11301131 let mut item = <NftItemList<T>>::get(collection_id, item_id);11321133 ensure!(1134 sender == item.owner,1135 "sender parameter and item owner must be equal"1136 );11371138 // update balance1139 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1140 .checked_sub(1)1141 .unwrap();1142 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);11431144 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1145 .checked_add(1)1146 .unwrap();1147 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);11481149 // change owner1150 let old_owner = item.owner.clone();1151 item.owner = new_owner.clone();1152 <NftItemList<T>>::insert(collection_id, item_id, item);11531154 // update index collection1155 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;11561157 // reset approved list1158 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1159 Ok(())1160 }11611162 fn init_collection(item: &CollectionType<T::AccountId>){11631164 // check params1165 assert!(item.decimal_points <= 4, "decimal_points parameter must be lower than 4");1166 assert!(item.name.len() <= 64, "Collection name can not be longer than 63 char");1167 assert!(item.name.len() <= 256, "Collection description can not be longer than 255 char");1168 assert!(item.token_prefix.len() <= 16, "Token prefix can not be longer than 15 char");1169 1170 // Generate next collection ID1171 let next_id = CreatedCollectionCount::get()1172 .checked_add(1)1173 .expect("collection id error");1174 1175 CreatedCollectionCount::put(next_id); 1176 }11771178 fn init_nft_token(item: &NftItemType<T::AccountId>){11791180 let current_index = <ItemListIndex>::get(item.collection)1181 .checked_add(1)1182 .expect("Item list index id error");11831184 let item_owner = item.owner.clone();1185 let collection_id = item.collection.clone();1186 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();11871188 <ItemListIndex>::insert(collection_id, current_index);11891190 // Update balance1191 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1192 .checked_add(1)1193 .unwrap();1194 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1195 }11961197 fn init_fungible_token(item: &FungibleItemType<T::AccountId>){11981199 let current_index = <ItemListIndex>::get(item.collection)1200 .checked_add(1)1201 .expect("Item list index id error");1202 let owner = item.owner.clone();1203 let value = item.value as u64;12041205 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();12061207 <ItemListIndex>::insert(item.collection, current_index);12081209 // Update balance1210 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1211 .checked_add(value)1212 .unwrap();1213 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1214 }12151216 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>){12171218 let current_index = <ItemListIndex>::get(item.collection)1219 .checked_add(1)1220 .expect("Item list index id error");12211222 let value = item.owner.first().unwrap().fraction as u64;1223 let owner = item.owner.first().unwrap().owner.clone();12241225 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();12261227 <ItemListIndex>::insert(item.collection, current_index);12281229 // Update balance1230 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1231 .checked_add(value)1232 .unwrap();1233 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1234 }12351236 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {1237 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1238 if list_exists {1239 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1240 let item_contains = list.contains(&item_index.clone());12411242 if !item_contains {1243 list.push(item_index.clone());1244 }12451246 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1247 } else {1248 let mut itm = Vec::new();1249 itm.push(item_index.clone());1250 <AddressTokens<T>>::insert(collection_id, owner, itm);1251 }12521253 Ok(())1254 }12551256 fn remove_token_index(1257 collection_id: u64,1258 item_index: u64,1259 owner: T::AccountId,1260 ) -> DispatchResult {1261 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1262 if list_exists {1263 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1264 let item_contains = list.contains(&item_index.clone());12651266 if item_contains {1267 list.retain(|&item| item != item_index);1268 <AddressTokens<T>>::insert(collection_id, owner, list);1269 }1270 }12711272 Ok(())1273 }12741275 fn move_token_index(1276 collection_id: u64,1277 item_index: u64,1278 old_owner: T::AccountId,1279 new_owner: T::AccountId,1280 ) -> DispatchResult {1281 Self::remove_token_index(collection_id, item_index, old_owner)?;1282 Self::add_token_index(collection_id, item_index, new_owner)?;12831284 Ok(())1285 }1286}12871288////////////////////////////////////////////////////////////////////////////////////////////////////1289// Economic models12901291/// Fee multiplier.1292pub type Multiplier = FixedU128;12931294type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1295 <T as system::Trait>::AccountId,1296>>::Balance;1297type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1298 <T as system::Trait>::AccountId,1299>>::NegativeImbalance;13001301/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1302/// in the queue.1303#[derive(Encode, Decode, Clone, Eq, PartialEq)]1304pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1305 #[codec(compact)] BalanceOf<T>,1306);13071308impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1309 for ChargeTransactionPayment<T>1310{1311 #[cfg(feature = "std")]1312 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1313 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1314 }1315 #[cfg(not(feature = "std"))]1316 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1317 Ok(())1318 }1319}13201321impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1322where1323 T::Call:1324 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1325 BalanceOf<T>: Send + Sync + FixedPointOperand,1326{1327 /// utility constructor. Used only in client/factory code.1328 pub fn from(fee: BalanceOf<T>) -> Self {1329 Self(fee)1330 }13311332 pub fn traditional_fee(1333 len: usize,1334 info: &DispatchInfoOf<T::Call>,1335 tip: BalanceOf<T>,1336 ) -> BalanceOf<T>1337 where1338 T::Call: Dispatchable<Info = DispatchInfo>,1339 {1340 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1341 }13421343 fn withdraw_fee(1344 &self,1345 who: &T::AccountId,1346 call: &T::Call,1347 info: &DispatchInfoOf<T::Call>,1348 len: usize,1349 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1350 let tip = self.0;13511352 // Set fee based on call type. Creating collection costs 1 Unique.1353 // All other transactions have traditional fees so far1354 let fee = match call.is_sub_type() {1355 Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1356 _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1357 // _ => <BalanceOf<T>>::from(100)1358 };13591360 // Determine who is paying transaction fee based on ecnomic model1361 // Parse call to extract collection ID and access collection sponsor1362 let sponsor: T::AccountId = match call.is_sub_type() {1363 Some(Call::create_item(collection_id, _properties, _owner)) => {1364 <Collection<T>>::get(collection_id).sponsor1365 }1366 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1367 <Collection<T>>::get(collection_id).sponsor1368 }13691370 _ => T::AccountId::default(),1371 };13721373 let mut who_pays_fee: T::AccountId = sponsor.clone();1374 if sponsor == T::AccountId::default() {1375 who_pays_fee = who.clone();1376 }13771378 // Only mess with balances if fee is not zero.1379 if fee.is_zero() {1380 return Ok((fee, None));1381 }13821383 match <T as transaction_payment::Trait>::Currency::withdraw(1384 &who_pays_fee,1385 fee,1386 if tip.is_zero() {1387 WithdrawReason::TransactionPayment.into()1388 } else {1389 WithdrawReason::TransactionPayment | WithdrawReason::Tip1390 },1391 ExistenceRequirement::KeepAlive,1392 ) {1393 Ok(imbalance) => Ok((fee, Some(imbalance))),1394 Err(_) => Err(InvalidTransaction::Payment.into()),1395 }1396 }1397}13981399impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1400 for ChargeTransactionPayment<T>1401where1402 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1403 T::Call:1404 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1405{1406 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1407 type AccountId = T::AccountId;1408 type Call = T::Call;1409 type AdditionalSigned = ();1410 type Pre = (1411 BalanceOf<T>,1412 Self::AccountId,1413 Option<NegativeImbalanceOf<T>>,1414 BalanceOf<T>,1415 );1416 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1417 Ok(())1418 }14191420 fn validate(1421 &self,1422 who: &Self::AccountId,1423 call: &Self::Call,1424 info: &DispatchInfoOf<Self::Call>,1425 len: usize,1426 ) -> TransactionValidity {1427 let (fee, _) = self.withdraw_fee(who, call, info, len)?;14281429 let mut r = ValidTransaction::default();1430 // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1431 // will be a bit more than setting the priority to tip. For now, this is enough.1432 r.priority = fee.saturated_into::<TransactionPriority>();1433 Ok(r)1434 }14351436 fn pre_dispatch(1437 self,1438 who: &Self::AccountId,1439 call: &Self::Call,1440 info: &DispatchInfoOf<Self::Call>,1441 len: usize,1442 ) -> Result<Self::Pre, TransactionValidityError> {1443 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1444 Ok((self.0, who.clone(), imbalance, fee))1445 }14461447 fn post_dispatch(1448 pre: Self::Pre,1449 info: &DispatchInfoOf<Self::Call>,1450 post_info: &PostDispatchInfoOf<Self::Call>,1451 len: usize,1452 _result: &DispatchResult,1453 ) -> Result<(), TransactionValidityError> {1454 let (tip, who, imbalance, fee) = pre;1455 if let Some(payed) = imbalance {1456 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1457 len as u32, info, post_info, tip,1458 );1459 let refund = fee.saturating_sub(actual_fee);1460 let actual_payment =1461 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1462 &who, refund,1463 ) {1464 Ok(refund_imbalance) => {1465 // The refund cannot be larger than the up front payed max weight.1466 // `PostDispatchInfo::calc_unspent` guards against such a case.1467 match payed.offset(refund_imbalance) {1468 Ok(actual_payment) => actual_payment,1469 Err(_) => return Err(InvalidTransaction::Payment.into()),1470 }1471 }1472 // We do not recreate the account using the refund. The up front payment1473 // is gone in that case.1474 Err(_) => payed,1475 };1476 let imbalances = actual_payment.split(tip);1477 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1478 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1479 );1480 }1481 Ok(())1482 }1483}pallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -3,6 +3,8 @@
use crate::{ApprovePermissions, CollectionMode, AccessMode, Ownership};
use frame_support::{assert_noop, assert_ok};
+// Use cases tests region
+// #region
#[test]
fn create_nft_item() {
new_test_ext().execute_with(|| {
@@ -955,3 +957,833 @@
assert_eq!(TemplateModule::balance_count(1, 2), 1);
});
}
+
+// #endregion
+
+// Coverage tests region
+// #region
+
+#[test]
+fn owner_can_add_address_to_white_list() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<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