--- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -10,7 +10,7 @@ pub use frame_support::{ construct_runtime, decl_event, decl_module, decl_storage, dispatch::DispatchResult, - ensure, parameter_types, + ensure, fail, parameter_types, traits::{ Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness, WithdrawReason, @@ -747,68 +747,53 @@ pub fn create_item(origin, collection_id: u64, properties: Vec, owner: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::collection_exists(collection_id)?; - let target_collection = >::get(collection_id); - - if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { - ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection"); - Self::check_white_list(collection_id, &owner)?; - Self::check_white_list(collection_id, &sender)?; - } - match target_collection.mode - { - CollectionMode::NFT(_) => { + Self::collection_exists(collection_id)?; - // check size - ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); + let target_collection = >::get(collection_id); - // Create nft item - let item = NftItemType { - collection: collection_id, - owner: owner, - data: properties.clone(), - }; - - Self::add_nft_item(item)?; - - }, - CollectionMode::Fungible(_) => { - - // check size - ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type"); + Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?; + Self::validate_create_item_args(&target_collection, &properties)?; + Self::create_item_no_validation(collection_id, &target_collection, &properties, &owner)?; - let item = FungibleItemType { - collection: collection_id, - owner: owner, - value: (10 as u128).pow(target_collection.decimal_points) - }; - - Self::add_fungible_item(item)?; - }, - CollectionMode::ReFungible(_, _) => { - - // check size - ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); + Ok(()) + } - 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}); + /// 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. + /// + /// * properties: Array items properties. Each property is an array of bytes itself, see [create_item]. + /// + /// * owner: Address, initial owner of the NFT. + #[weight = 0] + pub fn create_multiple_items(origin, collection_id: u64, properties: Vec>, owner: T::AccountId) -> DispatchResult { - let item = ReFungibleItemType { - collection: collection_id, - owner: owner_list, - data: properties.clone() - }; + ensure!(properties.len() > 0, "Length of items properties must be greater than 0."); + let sender = ensure_signed(origin)?; - Self::add_refungible_item(item)?; - }, - _ => { ensure!(1 == 0,"just error"); } + 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 prop in &properties { + Self::validate_create_item_args(&target_collection, prop)?; + } + for prop in &properties { + Self::create_item_no_validation(collection_id, &target_collection, prop, &owner)?; + } Ok(()) } @@ -1078,6 +1063,94 @@ } 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, "Public minting is not allowed for this collection"); + Self::check_white_list(collection_id, owner)?; + Self::check_white_list(collection_id, sender)?; + } + + Ok(()) + } + + fn validate_create_item_args(collection: &CollectionType, properties: &Vec) -> DispatchResult { + + match collection.mode + { + CollectionMode::NFT(_) => { + + // check size + ensure!(collection.custom_data_size >= properties.len() as u32, "Size of item is too large") + }, + CollectionMode::Fungible(_) => { + + // check size + ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type") + }, + CollectionMode::ReFungible(_, _) => { + + // check size + ensure!(collection.custom_data_size >= properties.len() as u32, "Size of item is too large") + }, + _ => { + fail!("Unexpected collection mode") + } + } + + Ok(()) + } + + fn create_item_no_validation(collection_id: u64, collection: &CollectionType, properties: &Vec, owner: &T::AccountId) -> DispatchResult { + match collection.mode + { + CollectionMode::NFT(_) => { + + // Create nft item + let item = NftItemType { + collection: collection_id, + owner: owner.clone(), + data: properties.clone(), + }; + + Self::add_nft_item(item)?; + + }, + CollectionMode::Fungible(_) => { + + let item = FungibleItemType { + collection: collection_id, + owner: owner.clone(), + value: (10 as u128).pow(collection.decimal_points) + }; + + Self::add_fungible_item(item)?; + }, + CollectionMode::ReFungible(_, _) => { + + 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, + data: properties.clone() + }; + + Self::add_refungible_item(item)?; + }, + _ => { ensure!(1 == 0,"just error"); } + + }; + + // 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) --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -44,7 +44,51 @@ }); } +// Use cases tests region +// #region #[test] +fn create_nft_multiple_items() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); + + assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits { + collection_numbers_limit: 10, + account_token_ownership_limit: 10, + collections_admins_limit: 5, + custom_data_limit: 2048, + nft_sponsor_transfer_timeout: 15, + fungible_sponsor_transfer_timeout: 15, + refungible_sponsor_transfer_timeout: 15, + })); + + 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); + + let properties = [[1, 2, 3].to_vec(), [3, 2, 1].to_vec(), [3, 3, 3].to_vec()].to_vec(); + + assert_ok!(TemplateModule::create_multiple_items( + origin1.clone(), + 1, + properties.clone(), + 1 + )); + for (index, data) in properties.iter().enumerate() { + assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).data, *data); + } + }); +} + +#[test] fn create_refungible_item() { new_test_ext().execute_with(|| { let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); @@ -93,6 +137,57 @@ } #[test] +fn create_multiple_refungible_items() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::ReFungible(2000, 3); + + assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits { + collection_numbers_limit: 10, + account_token_ownership_limit: 10, + collections_admins_limit: 5, + custom_data_limit: 2048, + nft_sponsor_transfer_timeout: 15, + fungible_sponsor_transfer_timeout: 15, + refungible_sponsor_transfer_timeout: 15, + })); + + 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); + + let properties = [[1, 2, 3].to_vec(), [3, 2, 1].to_vec(), [3, 3, 3].to_vec()].to_vec(); + + assert_ok!(TemplateModule::create_multiple_items( + origin1.clone(), + 1, + properties.clone(), + 1 + )); + for (index, data) in properties.iter().enumerate() { + + let item = TemplateModule::refungible_item_id(1, (index + 1) as u64); + assert_eq!(item.data, *data); + assert_eq!( + item.owner[0], + Ownership { + owner: 1, + fraction: 1000 + } + ); + } + }); +} + +#[test] fn create_fungible_item() { new_test_ext().execute_with(|| { let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); @@ -127,12 +222,55 @@ 1 )); assert_eq!(TemplateModule::fungible_item_id(1, 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(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::Fungible(3); + + assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits { + collection_numbers_limit: 10, + account_token_ownership_limit: 10, + collections_admins_limit: 5, + custom_data_limit: 2048, + nft_sponsor_transfer_timeout: 15, + fungible_sponsor_transfer_timeout: 15, + refungible_sponsor_transfer_timeout: 15, + })); + + 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); + + let properties = [[].to_vec(), [].to_vec(), [].to_vec()].to_vec(); + + assert_ok!(TemplateModule::create_multiple_items( + origin1.clone(), + 1, + properties.clone(), + 1 + )); + + for (index, _) in properties.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(|| { let col_name1: Vec = "Test1\0".encode_utf16().collect::>();