--- a/pallets/nft/src/benchmarking.rs +++ b/pallets/nft/src/benchmarking.rs @@ -165,7 +165,7 @@ let token_prefix1: Vec = b"token_prefix1".to_vec(); let mode: CollectionMode = CollectionMode::NFT; let caller: T::AccountId = T::AccountId::from(whitelisted_caller()); - let nft_data = CreateNftData { + let mut nft_data = CreateNftData { const_data: vec![], variable_data: vec![] }; @@ -173,7 +173,7 @@ nft_data.const_data.push(10); nft_data.variable_data.push(10); } - let mut data = CreateItemData::NFT(nft_data); + let data = CreateItemData::NFT(nft_data); Nft::::create_collection(RawOrigin::Signed(caller.clone()).into(), col_name1.clone(), col_desc1.clone(), token_prefix1.clone(), mode.clone())?; }: create_item(RawOrigin::Signed(caller.clone()), 2, caller.clone(), data) --- a/pallets/nft/src/default_weights.rs +++ b/pallets/nft/src/default_weights.rs @@ -63,7 +63,7 @@ } fn create_item(s: usize, ) -> Weight { (130_000_000 as Weight) - .saturating_add((2135 as Weight).saturating_mul(s as Weight)) + .saturating_add((2135 as Weight).saturating_mul(s as Weight).saturating_mul(500 as Weight)) // 500 is temporary multiplier, fee for storage .saturating_add(DbWeight::get().reads(10 as Weight)) .saturating_add(DbWeight::get().writes(8 as Weight)) } --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -8,9 +8,9 @@ use codec::{Decode, Encode}; pub use frame_support::{ - construct_runtime, decl_event, decl_module, decl_storage, + construct_runtime, decl_event, decl_module, decl_storage, decl_error, dispatch::DispatchResult, - ensure, parameter_types, fail, + ensure, fail, parameter_types, traits::{ Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness, WithdrawReason, @@ -22,7 +22,6 @@ }, IsSubType, StorageValue, }; -// use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; use frame_system::{self as system, ensure_signed, ensure_root}; use sp_runtime::sp_std::prelude::Vec; @@ -116,13 +115,6 @@ #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CollectionAdminsType { - pub admin: AccountId, - pub collection_id: u64, -} - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct NftItemType { pub collection: u64, pub owner: AccountId, @@ -268,6 +260,59 @@ } } + +decl_error! { + /// Error for non-fungible-token module. + pub enum Error for Module { + /// Total collections bound exceeded + TotalCollectionsLimitExceeded, + /// Decimal_points parameter must be lower than 4 + CollectionDecimalPointLimitExceeded, + /// Collection name can not be longer than 63 char + CollectionNameLimitExceeded, + /// Collection description can not be longer than 255 char + CollectionDescriptionLimitExceeded, + /// Token prefix can not be longer than 15 char + CollectionTokenPrefixLimitExceeded, + /// This collection does not exist + CollectionNotFound, + /// Item not exists + TokenNotFound, + /// Arithmetic calculation overflow + NumOverflow, + /// Account already has admin role + AlreadyAdmin, + /// You do not own this collection + NoPermission, + /// This address is not set as sponsor, use setCollectionSponsor first + ConfirmUnsetSponsorFail, + /// Collection is not in mint mode + PublicMintingNotAllowed, + /// Sender parameter and item owner must be equal + MustBeTokenOwner, + /// Item balance not enouth + TokenValueTooLow, + /// Size of item is too large + NftSizeLimitExceeded, + /// Size of item must be 0 with fungible type + FungibleUnexpectedParam, + /// No approve found + ApproveNotFound, + /// Requested value more than approved + TokenValueNotEnough, + /// Only approved addresses can call this method + ApproveRequired, + /// Address is not in white list + AddresNotInWhiteList, + /// Number of collection admins bound exceeded + CollectionAdminsLimitExceeded, + /// Owned tokens by a single address bound exceeded + AddressOwnershipLimitExceeded, + /// Length of items properties must be greater than 0 + EmptyArgument, + } +} + pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait { type Event: From> + Into<::Event>; @@ -386,6 +431,7 @@ pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; + type Error = Error; fn on_initialize(now: T::BlockNumber) -> Weight { @@ -432,32 +478,32 @@ }; // bound Total number of collections - ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded"); + ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::::TotalCollectionsLimitExceeded); // check params - ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4"); + ensure!(decimal_points <= 4, Error::::CollectionDecimalPointLimitExceeded); let mut name = collection_name.to_vec(); name.push(0); - ensure!(name.len() <= 64, "Collection name can not be longer than 63 char"); + ensure!(name.len() <= 64, Error::::CollectionNameLimitExceeded); let mut description = collection_description.to_vec(); description.push(0); - ensure!(name.len() <= 256, "Collection description can not be longer than 255 char"); + ensure!(name.len() <= 256, Error::::CollectionDescriptionLimitExceeded); let mut prefix = token_prefix.to_vec(); prefix.push(0); - ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char"); + ensure!(prefix.len() <= 16, Error::::CollectionTokenPrefixLimitExceeded); // Generate next collection ID let next_id = CreatedCollectionCount::get() .checked_add(1) - .expect("collection id error"); + .ok_or(Error::::NumOverflow)?; // bound counter let total = CollectionCount::get() .checked_add(1) - .expect("collection counter error"); + .ok_or(Error::::NumOverflow)?; CreatedCollectionCount::put(next_id); CollectionCount::put(total); @@ -489,7 +535,7 @@ } /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money. - /// + /// /// # Permissions /// /// * Collection Owner. @@ -524,7 +570,7 @@ // bound couter let total = CollectionCount::get() .checked_sub(1) - .expect("collection counter error"); + .ok_or(Error::::NumOverflow)?; CollectionCount::put(total); } @@ -693,11 +739,11 @@ if >::contains_key(collection_id) { admin_arr = >::get(collection_id); - ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role"); + ensure!(!admin_arr.contains(&new_admin_id), Error::::AlreadyAdmin); } // Number of collection admins - ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded"); + ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::::CollectionAdminsLimitExceeded); admin_arr.push(new_admin_id); >::insert(collection_id, admin_arr); @@ -746,10 +792,10 @@ pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); + ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.owner, "You do not own this collection"); + ensure!(sender == target_collection.owner, Error::::NoPermission); target_collection.unconfirmed_sponsor = new_sponsor; >::insert(collection_id, target_collection); @@ -768,10 +814,10 @@ pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); + ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first"); + ensure!(sender == target_collection.unconfirmed_sponsor, Error::::ConfirmUnsetSponsorFail); target_collection.sponsor = target_collection.unconfirmed_sponsor; target_collection.unconfirmed_sponsor = T::AccountId::default(); @@ -793,10 +839,10 @@ pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); + ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.owner, "You do not own this collection"); + ensure!(sender == target_collection.owner, Error::::NoPermission); target_collection.sponsor = T::AccountId::default(); >::insert(collection_id, target_collection); @@ -832,79 +878,55 @@ pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult { let sender = ensure_signed(origin)?; + Self::collection_exists(collection_id)?; + let target_collection = >::get(collection_id); - if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { - ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection."); - Self::check_white_list(collection_id, &owner)?; - Self::check_white_list(collection_id, &sender)?; - } + Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?; + Self::validate_create_item_args(&target_collection, &data)?; + Self::create_item_no_validation(collection_id, &target_collection, owner, data)?; + + Ok(()) + } - match target_collection.mode - { - CollectionMode::NFT => { - if let CreateItemData::NFT(data) = data { - // check sizes - ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit."); - ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit."); - - // Create nft item - let item = NftItemType { - collection: collection_id, - owner: owner, - const_data: data.const_data.clone(), - variable_data: data.variable_data.clone() - }; - - Self::add_nft_item(item)?; - - } else { - fail!("Not NFT item data used to mint in NFT collection."); - } - }, - CollectionMode::Fungible(_) => { - if let CreateItemData::Fungible(_) = data { - - let item = FungibleItemType { - collection: collection_id, - owner: owner, - value: (10 as u128).pow(target_collection.decimal_points) - }; - - Self::add_fungible_item(item)?; - } else { - fail!("Not Fungible item data used to mint in Fungible collection."); - } - }, - CollectionMode::ReFungible(_) => { - if let CreateItemData::ReFungible(data) = data { - - // check sizes - ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit."); - ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit."); - - let mut owner_list = Vec::new(); - let value = (10 as u128).pow(target_collection.decimal_points); - owner_list.push(Ownership {owner: owner.clone(), fraction: value}); - - let item = ReFungibleItemType { - collection: collection_id, - owner: owner_list, - const_data: data.const_data.clone(), - variable_data: data.variable_data.clone() - }; - - Self::add_refungible_item(item)?; - } else { - fail!("Not Re Fungible item data used to mint in Re Fungible collection."); - } - }, - _ => { ensure!(1 == 0,"Unexpected collection type."); } - }; + /// This method creates multiple instances of NFT Collection created with CreateCollection method. + /// + /// # Permissions + /// + /// * Collection Owner. + /// * Collection Admin. + /// * Anyone if + /// * White List is enabled, and + /// * Address is added to white list, and + /// * MintPermission is enabled (see SetMintPermission method) + /// + /// # Arguments + /// + /// * collection_id: ID of the collection. + /// + /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item]. + /// + /// * owner: Address, initial owner of the NFT. + #[weight = T::WeightInfo::create_item(items_data.into_iter() + .map(|data| { data.len() }) + .sum())] + pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec) -> DispatchResult { + + ensure!(items_data.len() > 0, Error::::EmptyArgument); + let sender = ensure_signed(origin)?; + + Self::collection_exists(collection_id)?; + let target_collection = >::get(collection_id); + + Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?; - // call event - Self::deposit_event(RawEvent::ItemCreated(collection_id, ::get(collection_id))); + for data in &items_data { + Self::validate_create_item_args(&target_collection, data)?; + } + for data in &items_data { + Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?; + } Ok(()) } @@ -932,7 +954,7 @@ let target_collection = >::get(collection_id); ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), - "Only item owner, collection owner and admins can modify item"); + Error::::NoPermission); if target_collection.access == AccessMode::WhiteList { Self::check_white_list(collection_id, &sender)?; @@ -984,7 +1006,7 @@ let target_collection = >::get(collection_id); ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), - "Only item owner, collection owner and admins can modify item"); + Error::::NoPermission); if target_collection.access == AccessMode::WhiteList { Self::check_white_list(collection_id, &sender)?; @@ -1026,7 +1048,7 @@ let target_collection = >::get(collection_id); ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), - "Only item owner, collection owner and admins can approve"); + Error::::NoPermission); if target_collection.access == AccessMode::WhiteList { Self::check_white_list(collection_id, &sender)?; @@ -1088,14 +1110,14 @@ if opt_item.is_some() { appoved_transfer = true; - ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved"); + ensure!(opt_item.unwrap().amount >= value, Error::::TokenValueNotEnough); } } // Transfer permissions check let target_collection = >::get(collection_id); - ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), - "Only item owner, collection owner and admins can modify items"); + ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + Error::::NoPermission); if target_collection.access == AccessMode::WhiteList { Self::check_white_list(collection_id, &sender)?; @@ -1134,7 +1156,7 @@ Ok(()) } - + /// Set off-chain data schema. /// /// # Permissions @@ -1162,7 +1184,7 @@ let target_collection = >::get(collection_id); ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), - "Only item owner, collection owner and admins can modify item."); + Error::::NoPermission); Self::item_exists(collection_id, item_id, &target_collection.mode)?; @@ -1296,7 +1318,7 @@ let owner = >::get(&contract_address); is_owner = sender == owner; } - ensure!(is_owner, "Only contract owner may call this method"); + ensure!(is_owner, Error::::NoPermission); >::insert(contract_address, enable); Ok(()) @@ -1306,10 +1328,101 @@ } impl Module { + + fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult { + + if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + ensure!(collection.mint_mode == true, Error::::PublicMintingNotAllowed); + Self::check_white_list(collection_id, owner)?; + Self::check_white_list(collection_id, sender)?; + } + + Ok(()) + } + + fn validate_create_item_args(target_collection: &CollectionType, data: &CreateItemData) -> DispatchResult { + match target_collection.mode + { + CollectionMode::NFT => { + if let CreateItemData::NFT(data) = data { + // check sizes + ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit."); + ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit."); + } else { + fail!("Not NFT item data used to mint in NFT collection."); + } + }, + CollectionMode::Fungible(_) => { + if let CreateItemData::Fungible(_) = data { + } else { + fail!("Not Fungible item data used to mint in Fungible collection."); + } + }, + CollectionMode::ReFungible(_) => { + if let CreateItemData::ReFungible(data) = data { + + // check sizes + ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit."); + ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit."); + } else { + fail!("Not Re Fungible item data used to mint in Re Fungible collection."); + } + }, + _ => { fail!("Unexpected collection type."); } + }; + + Ok(()) + } + + fn create_item_no_validation(collection_id: u64, collection: &CollectionType, owner: T::AccountId, data: CreateItemData) -> DispatchResult { + match data + { + CreateItemData::NFT(data) => { + let item = NftItemType { + collection: collection_id, + owner, + const_data: data.const_data, + variable_data: data.variable_data + }; + + Self::add_nft_item(item)?; + }, + CreateItemData::Fungible(_) => { + let item = FungibleItemType { + collection: collection_id, + owner, + value: (10 as u128).pow(collection.decimal_points) + }; + + Self::add_fungible_item(item)?; + }, + CreateItemData::ReFungible(data) => { + let mut owner_list = Vec::new(); + let value = (10 as u128).pow(collection.decimal_points); + owner_list.push(Ownership {owner: owner.clone(), fraction: value}); + + let item = ReFungibleItemType { + collection: collection_id, + owner: owner_list, + const_data: data.const_data, + variable_data: data.variable_data + }; + + Self::add_refungible_item(item)?; + } + }; + + + // call event + Self::deposit_event(RawEvent::ItemCreated(collection_id, ::get(collection_id))); + + Ok(()) + } + fn add_fungible_item(item: FungibleItemType) -> DispatchResult { let current_index = ::get(item.collection) .checked_add(1) - .expect("Item list index id error"); + .ok_or(Error::::NumOverflow)?; let itemcopy = item.clone(); let owner = item.owner.clone(); let value = item.value as u64; @@ -1326,7 +1439,7 @@ // Update balance let new_balance = >::get(item.collection, owner.clone()) .checked_add(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(item.collection, owner.clone(), new_balance); Ok(()) @@ -1335,7 +1448,7 @@ fn add_refungible_item(item: ReFungibleItemType) -> DispatchResult { let current_index = ::get(item.collection) .checked_add(1) - .expect("Item list index id error"); + .ok_or(Error::::NumOverflow)?; let itemcopy = item.clone(); let value = item.owner.first().unwrap().fraction as u64; @@ -1353,7 +1466,7 @@ // Update balance let new_balance = >::get(item.collection, owner.clone()) .checked_add(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(item.collection, owner.clone(), new_balance); Ok(()) @@ -1362,7 +1475,7 @@ fn add_nft_item(item: NftItemType) -> DispatchResult { let current_index = ::get(item.collection) .checked_add(1) - .expect("Item list index id error"); + .ok_or(Error::::NumOverflow)?; let item_owner = item.owner.clone(); let collection_id = item.collection.clone(); @@ -1378,7 +1491,7 @@ // Update balance let new_balance = >::get(collection_id, item_owner.clone()) .checked_add(1) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item_owner.clone(), new_balance); Ok(()) @@ -1391,7 +1504,7 @@ ) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), - "Item does not exists" + Error::::TokenNotFound ); let collection = >::get(collection_id, item_id); let item = collection @@ -1408,7 +1521,7 @@ // update balance let new_balance = >::get(collection_id, item.owner.clone()) .checked_sub(item.fraction as u64) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item.owner.clone(), new_balance); >::remove(collection_id, item_id); @@ -1419,7 +1532,7 @@ fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), - "Item does not exists" + Error::::TokenNotFound ); let item = >::get(collection_id, item_id); Self::remove_token_index(collection_id, item_id, item.owner.clone())?; @@ -1430,7 +1543,7 @@ // update balance let new_balance = >::get(collection_id, item.owner.clone()) .checked_sub(1) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item.owner.clone(), new_balance); >::remove(collection_id, item_id); @@ -1440,7 +1553,7 @@ fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), - "Item does not exists" + Error::::TokenNotFound ); let item = >::get(collection_id, item_id); Self::remove_token_index(collection_id, item_id, item.owner.clone())?; @@ -1451,7 +1564,7 @@ // update balance let new_balance = >::get(collection_id, item.owner.clone()) .checked_sub(item.value as u64) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item.owner.clone(), new_balance); >::remove(collection_id, item_id); @@ -1462,7 +1575,7 @@ fn collection_exists(collection_id: u64) -> DispatchResult { ensure!( >::contains_key(collection_id), - "This collection does not exist" + Error::::CollectionNotFound ); Ok(()) } @@ -1473,7 +1586,7 @@ let target_collection = >::get(collection_id); ensure!( subject == target_collection.owner, - "You do not own this collection" + Error::::NoPermission ); Ok(()) @@ -1502,7 +1615,7 @@ ensure!( result, - "You do not have permissions to modify this collection" + Error::::NoPermission ); Ok(()) } @@ -1528,7 +1641,7 @@ } fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult { - let mes = "Address is not in white list"; + let mes = Error::::AddresNotInWhiteList; ensure!(>::contains_key(collection_id), mes); let wl = >::get(collection_id); ensure!(wl.contains(address), mes); @@ -1545,18 +1658,18 @@ ) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), - "Item not exists" + Error::::TokenNotFound ); let full_item = >::get(collection_id, item_id); let amount = full_item.value; - ensure!(amount >= value.into(), "Item balance not enouth"); + ensure!(amount >= value.into(), Error::::TokenValueTooLow); // update balance let balance_old_owner = >::get(collection_id, owner.clone()) .checked_sub(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, owner.clone(), balance_old_owner); let mut new_owner_account_id = 0; @@ -1578,7 +1691,7 @@ // update balance let balance_new_owner = >::get(collection_id, new_owner.clone()) .checked_add(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, new_owner.clone(), balance_new_owner); // update index collection @@ -1596,7 +1709,7 @@ // update balance let balance_new_owner = >::get(collection_id, new_owner.clone()) .checked_add(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, new_owner.clone(), balance_new_owner); >::insert(collection_id, new_owner_account_id, item); @@ -1634,7 +1747,7 @@ ) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), - "Item not exists" + Error::::TokenNotFound ); let full_item = >::get(collection_id, item_id); @@ -1643,20 +1756,20 @@ .iter() .filter(|i| i.owner == owner) .next() - .unwrap(); + .ok_or(Error::::NumOverflow)?; let amount = item.fraction; - ensure!(amount >= value.into(), "Item balance not enouth"); + ensure!(amount >= value.into(), Error::::TokenValueTooLow); // update balance let balance_old_owner = >::get(collection_id, item.owner.clone()) .checked_sub(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item.owner.clone(), balance_old_owner); let balance_new_owner = >::get(collection_id, new_owner.clone()) .checked_add(value) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, new_owner.clone(), balance_new_owner); let old_owner = item.owner.clone(); @@ -1719,25 +1832,25 @@ ) -> DispatchResult { ensure!( >::contains_key(collection_id, item_id), - "Item not exists" + Error::::TokenNotFound ); let mut item = >::get(collection_id, item_id); ensure!( sender == item.owner, - "sender parameter and item owner must be equal" + Error::::MustBeTokenOwner ); // update balance let balance_old_owner = >::get(collection_id, item.owner.clone()) .checked_sub(1) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item.owner.clone(), balance_old_owner); let balance_new_owner = >::get(collection_id, new_owner.clone()) .checked_add(1) - .unwrap(); + .ok_or(Error::::NumOverflow)?; >::insert(collection_id, new_owner.clone(), balance_new_owner); // change owner @@ -1759,9 +1872,9 @@ mode: &CollectionMode ) -> DispatchResult { match mode { - CollectionMode::NFT => ensure!(>::contains_key(collection_id, item_id), "Item does not exists"), - CollectionMode::ReFungible(_) => ensure!(>::contains_key(collection_id, item_id), "Item does not exists"), - CollectionMode::Fungible(_) => ensure!(>::contains_key(collection_id, item_id), "Item does not exists"), + CollectionMode::NFT => ensure!(>::contains_key(collection_id, item_id), Error::::TokenNotFound), + CollectionMode::ReFungible(_) => ensure!(>::contains_key(collection_id, item_id), Error::::TokenNotFound), + CollectionMode::Fungible(_) => ensure!(>::contains_key(collection_id, item_id), Error::::TokenNotFound), _ => () }; @@ -1818,7 +1931,7 @@ // Generate next collection ID let next_id = CreatedCollectionCount::get() .checked_add(1) - .expect("collection id error"); + .unwrap(); CreatedCollectionCount::put(next_id); } @@ -1826,7 +1939,7 @@ fn init_nft_token(item: &NftItemType) { let current_index = ::get(item.collection) .checked_add(1) - .expect("Item list index id error"); + .unwrap(); let item_owner = item.owner.clone(); let collection_id = item.collection.clone(); @@ -1844,7 +1957,7 @@ fn init_fungible_token(item: &FungibleItemType) { let current_index = ::get(item.collection) .checked_add(1) - .expect("Item list index id error"); + .unwrap(); let owner = item.owner.clone(); let value = item.value as u64; @@ -1862,7 +1975,7 @@ fn init_refungible_token(item: &ReFungibleItemType) { let current_index = ::get(item.collection) .checked_add(1) - .expect("Item list index id error"); + .unwrap(); let value = item.owner.first().unwrap().fraction as u64; let owner = item.owner.first().unwrap().owner.clone(); @@ -1885,10 +1998,11 @@ // bound Owned tokens by a single address let count = >::get(owner.clone()); - ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded"); + ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::::AddressOwnershipLimitExceeded); - >::insert(owner.clone(), - count.checked_add(1).unwrap()); + >::insert(owner.clone(), count + .checked_add(1) + .ok_or(Error::::NumOverflow)?); } else { >::insert(owner.clone(), 1); @@ -1922,7 +2036,9 @@ // update counter >::insert(owner.clone(), - >::get(owner.clone()).checked_sub(1).unwrap()); + >::get(owner.clone()) + .checked_sub(1) + .ok_or(Error::::NumOverflow)?); let list_exists = >::contains_key(collection_id, owner.clone()); @@ -2236,5 +2352,3 @@ } // #endregion - - --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -87,6 +87,32 @@ }); } +// Use cases tests region +// #region +#[test] +fn create_nft_multiple_items() { + new_test_ext().execute_with(|| { + default_limits(); + + create_test_collection(&CollectionMode::NFT, 1); + + let origin1 = Origin::signed(1); + + let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()]; + + assert_ok!(TemplateModule::create_multiple_items( + origin1.clone(), + 1, + 1, + items_data.clone().into_iter().map(|d| { d.into() }).collect() + )); + for (index, data) in items_data.iter().enumerate() { + assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).const_data.to_vec(), data.const_data); + assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).variable_data.to_vec(), data.variable_data); + } + }); +} + #[test] fn create_refungible_item() { new_test_ext().execute_with(|| { @@ -114,6 +140,39 @@ } #[test] +fn create_multiple_refungible_items() { + new_test_ext().execute_with(|| { + default_limits(); + + create_test_collection(&CollectionMode::ReFungible(3), 1); + + let origin1 = Origin::signed(1); + + let items_data = vec![default_re_fungible_data(), default_re_fungible_data(), default_re_fungible_data()]; + + assert_ok!(TemplateModule::create_multiple_items( + origin1.clone(), + 1, + 1, + items_data.clone().into_iter().map(|d| { d.into() }).collect() + )); + for (index, data) in items_data.iter().enumerate() { + + let item = TemplateModule::refungible_item_id(1, (index + 1) as u64); + assert_eq!(item.const_data.to_vec(), data.const_data); + assert_eq!(item.variable_data.to_vec(), data.variable_data); + assert_eq!( + item.owner[0], + Ownership { + owner: 1, + fraction: 1000 + } + ); + } + }); +} + +#[test] fn create_fungible_item() { new_test_ext().execute_with(|| { default_limits(); @@ -124,12 +183,36 @@ create_test_item(collection_id, &data.into()); assert_eq!(TemplateModule::fungible_item_id(collection_id, 1).owner, 1); - assert_eq!(TemplateModule::balance_count(1, 1), 1000); - assert_eq!(TemplateModule::address_tokens(1, 1), [1]); }); } #[test] +fn create_multiple_fungible_items() { + new_test_ext().execute_with(|| { + default_limits(); + + create_test_collection(&CollectionMode::Fungible(3), 1); + + let origin1 = Origin::signed(1); + + let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()]; + + assert_ok!(TemplateModule::create_multiple_items( + origin1.clone(), + 1, + 1, + items_data.clone().into_iter().map(|d| { d.into() }).collect() + )); + + for (index, _) in items_data.iter().enumerate() { + assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as u64).owner, 1); + } + assert_eq!(TemplateModule::balance_count(1, 1), 3000); + assert_eq!(TemplateModule::address_tokens(1, 1), [1, 2, 3]); + }); +} + +#[test] fn transfer_fungible_item() { new_test_ext().execute_with(|| { default_limits(); @@ -1333,7 +1416,7 @@ assert_noop!( TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()), - "Public minting is not allowed for this collection." + "Public minting is not allowed for this collection" ); }); } @@ -1362,7 +1445,7 @@ assert_noop!( TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()), - "Public minting is not allowed for this collection." + "Public minting is not allowed for this collection" ); }); } --- a/runtime/src/nft_weights.rs +++ b/runtime/src/nft_weights.rs @@ -64,7 +64,7 @@ } fn create_item(s: usize, ) -> Weight { (130_000_000 as Weight) - .saturating_add((2135 as Weight).saturating_mul(s as Weight)) + .saturating_add((2135 as Weight).saturating_mul(s as Weight).saturating_mul(500 as Weight)) // 500 is temparary multiplier, fee for storage .saturating_add(DbWeight::get().reads(10 as Weight)) .saturating_add(DbWeight::get().writes(8 as Weight)) }