--- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -180,9 +180,9 @@ .collect(), }), pallet_nft: Some(NftConfig { - collection: vec![( + collection_id: vec![( 1, - CollectionType { + Collection { owner: get_account_id_from_seed::("Alice"), mode: CollectionMode::NFT, access: AccessMode::Normal, --- a/pallets/nft/src/default_weights.rs +++ b/pallets/nft/src/default_weights.rs @@ -127,6 +127,11 @@ .saturating_add(DbWeight::get().reads(0 as Weight)) .saturating_add(DbWeight::get().writes(2 as Weight)) } + fn set_variable_meta_data_sponsoring_rate_limit() -> Weight { + (3_500_000 as Weight) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } fn toggle_contract_white_list() -> Weight { (3_000_000 as Weight) .saturating_add(DbWeight::get().reads(0 as Weight)) --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -13,6 +13,7 @@ #[cfg(feature = "std")] pub use serde::*; +use core::ops::{Deref, DerefMut}; use codec::{Decode, Encode}; pub use frame_support::{ construct_runtime, decl_event, decl_module, decl_storage, decl_error, @@ -160,10 +161,10 @@ } } -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[derive(Encode, Decode, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CollectionType { - pub owner: AccountId, +pub struct Collection { + pub owner: T::AccountId, pub mode: CollectionMode, pub access: AccessMode, pub decimal_points: DecimalPoints, @@ -174,11 +175,30 @@ pub offchain_schema: Vec, pub schema_version: SchemaVersion, pub sponsorship: SponsorshipState, - pub limits: CollectionLimits, // Collection private restrictions + pub limits: CollectionLimits, // Collection private restrictions pub variable_on_chain_schema: Vec, // pub const_on_chain_schema: Vec, // } +pub struct CollectionHandle { + pub id: CollectionId, + collection: Collection, +} + +impl Deref for CollectionHandle { + type Target = Collection; + + fn deref(&self) -> &Self::Target { + &self.collection + } +} + +impl DerefMut for CollectionHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.collection + } +} + #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct NftItemType { @@ -214,9 +234,13 @@ #[derive(Encode, Decode, Debug, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CollectionLimits { +pub struct CollectionLimits { pub account_token_ownership_limit: u32, pub sponsored_data_size: u32, + /// None - setVariableMetadata is not sponsored + /// Some(v) - setVariableMetadata is sponsored + /// if there is v block between txs + pub sponsored_data_rate_limit: Option, pub token_limit: u32, // Timeouts for item types in passed blocks @@ -225,12 +249,13 @@ pub owner_can_destroy: bool, } -impl Default for CollectionLimits { - fn default() -> CollectionLimits { - CollectionLimits { +impl Default for CollectionLimits { + fn default() -> Self { + Self { account_token_ownership_limit: 10_000_000, token_limit: u32::max_value(), - sponsored_data_size: u32::MAX, + sponsored_data_size: u32::MAX, + sponsored_data_rate_limit: None, sponsor_transfer_timeout: 14400, owner_can_transfer: true, owner_can_destroy: true @@ -283,6 +308,7 @@ fn set_schema_version() -> Weight; fn set_chain_limits() -> Weight; fn set_contract_sponsoring_rate_limit() -> Weight; + fn set_variable_meta_data_sponsoring_rate_limit() -> Weight; fn toggle_contract_white_list() -> Weight; fn add_to_contract_white_list() -> Weight; fn remove_from_contract_white_list() -> Weight; @@ -496,7 +522,7 @@ //#region Basic collections /// Collection info /// Collection id (controlled?1) - pub Collection get(fn collection) config(): map hasher(blake2_128_concat) CollectionId => CollectionType; + pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option> = None; /// List of collection admins /// Collection id (controlled?2) pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec; @@ -538,6 +564,10 @@ pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber; //#endregion + /// Variable metadata sponsoring + /// Collection id (controlled?2), token id (controlled?2) + pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option = None; + //#region Contract Sponsorship and Ownership /// Contract address (real) pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId; @@ -556,7 +586,7 @@ add_extra_genesis { build(|config: &GenesisConfig| { // Modification of storage - for (_num, _c) in &config.collection { + for (_num, _c) in &config.collection_id { >::init_collection(_c); } @@ -710,7 +740,7 @@ }; // Create new collection - let new_collection = CollectionType { + let new_collection = Collection { owner: who.clone(), name: collection_name, mode: mode.clone(), @@ -728,7 +758,7 @@ }; // Add new collection to map - >::insert(next_id, new_collection); + >::insert(next_id, new_collection); // call event Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone())); @@ -750,10 +780,9 @@ pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_permissions(collection_id, sender)?; - - let target_collection = >::get(collection_id); - if !target_collection.limits.owner_can_destroy { + let collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&collection, sender)?; + if !collection.limits.owner_can_destroy { fail!(Error::::NoPermission); } @@ -762,7 +791,7 @@ >::remove_prefix(collection_id); ::remove(collection_id); >::remove(collection_id); - >::remove(collection_id); + >::remove(collection_id); >::remove_prefix(collection_id); >::remove_prefix(collection_id); @@ -773,6 +802,8 @@ >::remove_prefix(collection_id); >::remove_prefix(collection_id); + >::remove_prefix(collection_id); + DestroyedCollectionCount::put(DestroyedCollectionCount::get() .checked_add(1) .ok_or(Error::::NumOverflow)?); @@ -797,7 +828,8 @@ pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{ let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender)?; + let collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&collection, sender)?; >::insert(collection_id, address, true); @@ -821,7 +853,8 @@ pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{ let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender)?; + let collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&collection, sender)?; >::remove(collection_id, address); @@ -845,10 +878,10 @@ { let sender = ensure_signed(origin)?; - Self::check_owner_permissions(collection_id, sender)?; - let mut target_collection = >::get(collection_id); + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&target_collection, sender)?; target_collection.access = mode; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -872,10 +905,10 @@ { let sender = ensure_signed(origin)?; - Self::check_owner_permissions(collection_id, sender)?; - let mut target_collection = >::get(collection_id); + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&target_collection, sender)?; target_collection.mint_mode = mint_permission; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -896,10 +929,10 @@ pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_permissions(collection_id, sender)?; - let mut target_collection = >::get(collection_id); + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&target_collection, sender)?; target_collection.owner = new_owner; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -922,7 +955,8 @@ pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender)?; + let collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&collection, sender)?; let mut admin_arr: Vec = Vec::new(); if >::contains_key(collection_id) @@ -957,7 +991,8 @@ pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender)?; + let collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&collection, sender)?; ensure!(>::contains_key(collection_id), Error::::AdminNotFound); let mut admin_arr = >::get(collection_id); @@ -981,13 +1016,11 @@ pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); - - let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.owner, Error::::NoPermission); + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&target_collection, sender)?; target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor); - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1004,16 +1037,15 @@ pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); - let mut target_collection = >::get(collection_id); + let mut target_collection = Self::get_collection(collection_id)?; ensure!( target_collection.sponsorship.pending_sponsor() == Some(&sender), Error::::ConfirmUnsetSponsorFail ); target_collection.sponsorship = SponsorshipState::Confirmed(sender); - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1032,13 +1064,12 @@ pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); - let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.owner, Error::::NoPermission); + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&target_collection, sender)?; target_collection.sponsorship = SponsorshipState::Disabled; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1072,14 +1103,12 @@ pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult { let sender = ensure_signed(origin)?; - - Self::collection_exists(collection_id)?; - let target_collection = >::get(collection_id); + let target_collection = Self::get_collection(collection_id)?; - Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner, 1)?; + Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?; Self::validate_create_item_args(&target_collection, &data)?; - Self::create_item_no_validation(collection_id, owner, data)?; + Self::create_item_no_validation(&target_collection, owner, data)?; Ok(()) } @@ -1111,16 +1140,15 @@ ensure!(items_data.len() > 0, Error::::EmptyArgument); let sender = ensure_signed(origin)?; - Self::collection_exists(collection_id)?; - let target_collection = >::get(collection_id); + let target_collection = Self::get_collection(collection_id)?; - Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner, items_data.len() as u32)?; + Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?; 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, owner.clone(), data.clone())?; + Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?; } Ok(()) @@ -1144,33 +1172,32 @@ pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::collection_exists(collection_id)?; // Transfer permissions check - let target_collection = >::get(collection_id); + let target_collection = Self::get_collection(collection_id)?; ensure!( - Self::is_item_owner(sender.clone(), collection_id, item_id) || + Self::is_item_owner(sender.clone(), &target_collection, item_id) || ( target_collection.limits.owner_can_transfer && - Self::is_owner_or_admin_permissions(collection_id, sender.clone()) + Self::is_owner_or_admin_permissions(&target_collection, sender.clone()) ), Error::::NoPermission ); if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(collection_id, &sender)?; + Self::check_white_list(&target_collection, &sender)?; } match target_collection.mode { - CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?, - CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?, - CollectionMode::ReFungible => Self::burn_refungible_item(collection_id, item_id, &sender)?, + CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?, + CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?, + CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?, _ => () }; // call event - Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id)); + Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id)); Ok(()) } @@ -1202,7 +1229,9 @@ #[transactional] pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::transfer_internal(sender, recipient, collection_id, item_id, value) + let collection = Self::get_collection(collection_id)?; + + Self::transfer_internal(sender, recipient, &collection, item_id, value) } /// Set, change, or remove approved address to transfer the ownership of the NFT. @@ -1225,16 +1254,14 @@ pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult { let sender = ensure_signed(origin)?; + let target_collection = Self::get_collection(collection_id)?; - Self::collection_exists(collection_id)?; - Self::token_exists(collection_id, item_id, &sender)?; + Self::token_exists(&target_collection, item_id, &sender)?; // Transfer permissions check - let target_collection = >::get(collection_id); - let bypasses_limits = target_collection.limits.owner_can_transfer && Self::is_owner_or_admin_permissions( - collection_id, + &target_collection, sender.clone(), ); @@ -1242,7 +1269,7 @@ None } else if let Some(amount) = Self::owned_amount( sender.clone(), - collection_id, + &target_collection, item_id, ) { Some(amount) @@ -1251,8 +1278,8 @@ }; if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(collection_id, &sender)?; - Self::check_white_list(collection_id, &spender)?; + Self::check_white_list(&target_collection, &sender)?; + Self::check_white_list(&target_collection, &spender)?; } let allowance_exists = >::contains_key(collection_id, (item_id, &sender, &spender)); @@ -1292,6 +1319,8 @@ pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult { let sender = ensure_signed(origin)?; + let target_collection = Self::get_collection(collection_id)?; + let mut appoved_transfer = false; // Check approval @@ -1302,24 +1331,22 @@ appoved_transfer = true; } - let target_collection = >::get(collection_id); - // Limits check - Self::is_correct_transfer(collection_id, &target_collection, &recipient)?; + Self::is_correct_transfer(&target_collection, &recipient)?; // Transfer permissions check ensure!( appoved_transfer || ( target_collection.limits.owner_can_transfer && - Self::is_owner_or_admin_permissions(collection_id, sender.clone()) + Self::is_owner_or_admin_permissions(&target_collection, sender.clone()) ), Error::::NoPermission ); if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(collection_id, &sender)?; - Self::check_white_list(collection_id, &recipient)?; + Self::check_white_list(&target_collection, &sender)?; + Self::check_white_list(&target_collection, &recipient)?; } // Reduce approval by transferred amount or remove if remaining approval drops to 0 @@ -1332,9 +1359,9 @@ match target_collection.mode { - CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?, - CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?, - CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?, + CollectionMode::NFT => Self::transfer_nft(&target_collection, item_id, from, recipient)?, + CollectionMode::Fungible(_) => Self::transfer_fungible(&target_collection, value, &from, &recipient)?, + CollectionMode::ReFungible => Self::transfer_refungible(&target_collection, item_id, value, from.clone(), recipient)?, _ => () }; @@ -1378,21 +1405,20 @@ ) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::collection_exists(collection_id)?; - Self::token_exists(collection_id, item_id, &sender)?; + let target_collection = Self::get_collection(collection_id)?; + Self::token_exists(&target_collection, item_id, &sender)?; ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::::TokenVariableDataLimitExceeded); // Modify permissions check - let target_collection = >::get(collection_id); - ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || - Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + ensure!(Self::is_item_owner(sender.clone(), &target_collection, item_id) || + Self::is_owner_or_admin_permissions(&target_collection, sender.clone()), Error::::NoPermission); match target_collection.mode { - CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?, - CollectionMode::ReFungible => Self::set_re_fungible_variable_data(collection_id, item_id, data)?, + CollectionMode::NFT => Self::set_nft_variable_data(&target_collection, item_id, data)?, + CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&target_collection, item_id, data)?, CollectionMode::Fungible(_) => fail!(Error::::CantStoreMetadataInFungibleTokens), _ => fail!(Error::::UnexpectedCollectionType) }; @@ -1422,10 +1448,10 @@ version: SchemaVersion ) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; - let mut target_collection = >::get(collection_id); + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?; target_collection.schema_version = version; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1450,14 +1476,14 @@ schema: Vec ) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?; // check schema limit ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, ""); - let mut target_collection = >::get(collection_id); target_collection.offchain_schema = schema; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1482,14 +1508,14 @@ schema: Vec ) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?; // check schema limit ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, ""); - let mut target_collection = >::get(collection_id); target_collection.const_on_chain_schema = schema; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1514,14 +1540,14 @@ schema: Vec ) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?; // check schema limit ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, ""); - let mut target_collection = >::get(collection_id); target_collection.variable_on_chain_schema = schema; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1694,12 +1720,12 @@ pub fn set_collection_limits( origin, collection_id: u32, - new_limits: CollectionLimits, + new_limits: CollectionLimits, ) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_owner_permissions(collection_id, sender.clone())?; - let mut target_collection = >::get(collection_id); - let old_limits = target_collection.limits; + let mut target_collection = Self::get_collection(collection_id)?; + Self::check_owner_permissions(&target_collection, sender.clone())?; + let old_limits = &target_collection.limits; let chain_limits = ChainLimit::get(); // collection bounds @@ -1719,7 +1745,7 @@ ); target_collection.limits = new_limits; - >::insert(collection_id, target_collection); + Self::save_collection(target_collection); Ok(()) } @@ -1728,38 +1754,36 @@ impl Module { - pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult { - - let target_collection = >::get(collection_id); - + pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle, item_id: TokenId, value: u128) -> DispatchResult { // Limits check - Self::is_correct_transfer(collection_id, &target_collection, &recipient)?; + Self::is_correct_transfer(target_collection, &recipient)?; // Transfer permissions check - ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || - Self::is_owner_or_admin_permissions(collection_id, sender.clone()), + ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) || + Self::is_owner_or_admin_permissions(target_collection, sender.clone()), Error::::NoPermission); if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(collection_id, &sender)?; - Self::check_white_list(collection_id, &recipient)?; + Self::check_white_list(target_collection, &sender)?; + Self::check_white_list(target_collection, &recipient)?; } match target_collection.mode { - CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient.clone())?, - CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?, - CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient.clone())?, + CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?, + CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?, + CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?, _ => () }; - Self::deposit_event(RawEvent::Transfer(collection_id, item_id, sender, recipient, value)); + Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value)); Ok(()) } - fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType, recipient: &T::AccountId) -> DispatchResult { + fn is_correct_transfer(collection: &CollectionHandle, recipient: &T::AccountId) -> DispatchResult { + let collection_id = collection.id; // check token limit and account token limit let account_items: u32 = >::get(collection_id, recipient).len() as u32; @@ -1768,7 +1792,8 @@ Ok(()) } - fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult { + fn can_create_items_in_collection(collection: &CollectionHandle, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult { + let collection_id = collection.id; // check token limit and account token limit let total_items: u32 = ItemListIndex::get(collection_id) @@ -1780,16 +1805,16 @@ ensure!(collection.limits.token_limit >= total_items, Error::::CollectionTokenLimitExceeded); ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::::AccountTokenLimitExceeded); - if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + if !Self::is_owner_or_admin_permissions(collection, sender.clone()) { ensure!(collection.mint_mode == true, Error::::PublicMintingNotAllowed); - Self::check_white_list(collection_id, owner)?; - Self::check_white_list(collection_id, sender)?; + Self::check_white_list(collection, owner)?; + Self::check_white_list(collection, sender)?; } Ok(()) } - fn validate_create_item_args(target_collection: &CollectionType, data: &CreateItemData) -> DispatchResult { + fn validate_create_item_args(target_collection: &CollectionHandle, data: &CreateItemData) -> DispatchResult { match target_collection.mode { CollectionMode::NFT => { @@ -1827,7 +1852,9 @@ Ok(()) } - fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult { + fn create_item_no_validation(collection: &CollectionHandle, owner: T::AccountId, data: CreateItemData) -> DispatchResult { + let collection_id = collection.id; + match data { CreateItemData::NFT(data) => { @@ -1837,10 +1864,10 @@ variable_data: data.variable_data }; - Self::add_nft_item(collection_id, item)?; + Self::add_nft_item(collection, item)?; }, CreateItemData::Fungible(data) => { - Self::add_fungible_item(collection_id, &owner, data.value)?; + Self::add_fungible_item(collection, &owner, data.value)?; }, CreateItemData::ReFungible(data) => { let mut owner_list = Vec::new(); @@ -1852,14 +1879,15 @@ variable_data: data.variable_data }; - Self::add_refungible_item(collection_id, item)?; + Self::add_refungible_item(collection, item)?; } }; Ok(()) } - fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult { + fn add_fungible_item(collection: &CollectionHandle, owner: &T::AccountId, value: u128) -> DispatchResult { + let collection_id = collection.id; // Does new owner already have an account? let mut balance: u128 = 0; @@ -1883,7 +1911,9 @@ Ok(()) } - fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType) -> DispatchResult { + fn add_refungible_item(collection: &CollectionHandle, item: ReFungibleItemType) -> DispatchResult { + let collection_id = collection.id; + let current_index = ::get(collection_id) .checked_add(1) .ok_or(Error::::NumOverflow)?; @@ -1913,7 +1943,9 @@ Ok(()) } - fn add_nft_item(collection_id: CollectionId, item: NftItemType) -> DispatchResult { + fn add_nft_item(collection: &CollectionHandle, item: NftItemType) -> DispatchResult { + let collection_id = collection.id; + let current_index = ::get(collection_id) .checked_add(1) .ok_or(Error::::NumOverflow)?; @@ -1935,10 +1967,12 @@ } fn burn_refungible_item( - collection_id: CollectionId, + collection: &CollectionHandle, item_id: TokenId, owner: &T::AccountId, ) -> DispatchResult { + let collection_id = collection.id; + ensure!( >::contains_key(collection_id, item_id), Error::::TokenNotFound @@ -1969,6 +2003,7 @@ // Burn the token completely if this was the last (only) owner if owner_count == 0 { >::remove(collection_id, item_id); + >::remove(collection_id, item_id); } else { >::insert(collection_id, item_id, token); @@ -1977,7 +2012,9 @@ Ok(()) } - fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult { + fn burn_nft_item(collection: &CollectionHandle, item_id: TokenId) -> DispatchResult { + let collection_id = collection.id; + ensure!( >::contains_key(collection_id, item_id), Error::::TokenNotFound @@ -1991,11 +2028,14 @@ .ok_or(Error::::NumOverflow)?; >::insert(collection_id, item.owner.clone(), new_balance); >::remove(collection_id, item_id); + >::remove(collection_id, item_id); Ok(()) } - fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult { + fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle, value: u128) -> DispatchResult { + let collection_id = collection.id; + ensure!( >::contains_key(collection_id, owner), Error::::TokenNotFound @@ -2020,18 +2060,20 @@ Ok(()) } - fn collection_exists(collection_id: CollectionId) -> DispatchResult { - ensure!( - >::contains_key(collection_id), - Error::::CollectionNotFound - ); - Ok(()) + pub fn get_collection(collection_id: CollectionId) -> Result, sp_runtime::DispatchError> { + Ok(>::get(collection_id) + .map(|collection| CollectionHandle { + id: collection_id, + collection + }) + .ok_or(Error::::CollectionNotFound)?) } - fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult { - Self::collection_exists(collection_id)?; + fn save_collection(collection: CollectionHandle) { + >::insert(collection.id, collection.collection); + } - let target_collection = >::get(collection_id); + fn check_owner_permissions(target_collection: &CollectionHandle, subject: T::AccountId) -> DispatchResult { ensure!( subject == target_collection.owner, Error::::NoPermission @@ -2040,13 +2082,12 @@ Ok(()) } - fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool { - let target_collection = >::get(collection_id); - let mut result: bool = subject == target_collection.owner; - let exists = >::contains_key(collection_id); + fn is_owner_or_admin_permissions(collection: &CollectionHandle, subject: T::AccountId) -> bool { + let mut result: bool = subject == collection.owner; + let exists = >::contains_key(collection.id); if !result & exists { - if >::get(collection_id).contains(&subject) { + if >::get(collection.id).contains(&subject) { result = true } } @@ -2055,11 +2096,10 @@ } fn check_owner_or_admin_permissions( - collection_id: CollectionId, + collection: &CollectionHandle, subject: T::AccountId, ) -> DispatchResult { - Self::collection_exists(collection_id)?; - let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone()); + let result = Self::is_owner_or_admin_permissions(collection, subject.clone()); ensure!( result, @@ -2070,10 +2110,10 @@ fn owned_amount( subject: T::AccountId, - collection_id: CollectionId, + target_collection: &CollectionHandle, item_id: TokenId, ) -> Option { - let target_collection = >::get(collection_id); + let collection_id = target_collection.id; match target_collection.mode { CollectionMode::NFT => { @@ -2098,8 +2138,8 @@ } } - fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool { - let target_collection = >::get(collection_id); + fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle, item_id: TokenId) -> bool { + let collection_id = target_collection.id; match target_collection.mode { CollectionMode::NFT => { @@ -2118,7 +2158,9 @@ } } - fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult { + fn check_white_list(collection: &CollectionHandle, address: &T::AccountId) -> DispatchResult { + let collection_id = collection.id; + let mes = Error::::AddresNotInWhiteList; ensure!(>::contains_key(collection_id, address), mes); @@ -2128,11 +2170,11 @@ /// Check if token exists. In case of Fungible, check if there is an entry for /// the owner in fungible balances double map fn token_exists( - collection_id: CollectionId, + target_collection: &CollectionHandle, item_id: TokenId, owner: &T::AccountId ) -> DispatchResult { - let target_collection = >::get(collection_id); + let collection_id = target_collection.id; let exists = match target_collection.mode { CollectionMode::NFT => >::contains_key(collection_id, item_id), @@ -2146,18 +2188,19 @@ } fn transfer_fungible( - collection_id: CollectionId, + collection: &CollectionHandle, value: u128, owner: &T::AccountId, recipient: &T::AccountId, ) -> DispatchResult { - Self::token_exists(collection_id, 0, owner)?; + let collection_id = collection.id; + Self::token_exists(&collection, 0, owner)?; let mut balance = >::get(collection_id, owner); ensure!(balance.value >= value, Error::::TokenValueTooLow); // Send balance to recipient (updates balanceOf of recipient) - Self::add_fungible_item(collection_id, recipient, value)?; + Self::add_fungible_item(collection, recipient, value)?; // update balanceOf of sender >::insert(collection_id, (*owner).clone(), balance.value - value); @@ -2175,13 +2218,14 @@ } fn transfer_refungible( - collection_id: CollectionId, + collection: &CollectionHandle, item_id: TokenId, value: u128, owner: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { - Self::token_exists(collection_id, item_id, &owner)?; + let collection_id = collection.id; + Self::token_exists(collection, item_id, &owner)?; let full_item = >::get(collection_id, item_id); let item = full_item @@ -2257,12 +2301,13 @@ } fn transfer_nft( - collection_id: CollectionId, + collection: &CollectionHandle, item_id: TokenId, sender: T::AccountId, new_owner: T::AccountId, ) -> DispatchResult { - Self::token_exists(collection_id, item_id, &sender)?; + let collection_id = collection.id; + Self::token_exists(&collection, item_id, &sender)?; let mut item = >::get(collection_id, item_id); @@ -2294,10 +2339,11 @@ } fn set_re_fungible_variable_data( - collection_id: CollectionId, + collection: &CollectionHandle, item_id: TokenId, data: Vec ) -> DispatchResult { + let collection_id = collection.id; let mut item = >::get(collection_id, item_id); item.variable_data = data; @@ -2308,10 +2354,11 @@ } fn set_nft_variable_data( - collection_id: CollectionId, + collection: &CollectionHandle, item_id: TokenId, data: Vec ) -> DispatchResult { + let collection_id = collection.id; let mut item = >::get(collection_id, item_id); item.variable_data = data; @@ -2321,7 +2368,7 @@ Ok(()) } - fn init_collection(item: &CollectionType) { + fn init_collection(item: &Collection) { // check params assert!( item.decimal_points <= MAX_DECIMAL_POINTS, @@ -2401,7 +2448,6 @@ } fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult { - // add to account limit if >::contains_key(owner) { @@ -2569,8 +2615,9 @@ // Determine who is paying transaction fee based on ecnomic model // Parse call to extract collection ID and access collection sponsor - let mut sponsor: T::AccountId = match IsSubType::>::is_sub_type(call) { + let mut sponsor: Option = (|| match IsSubType::>::is_sub_type(call) { Some(Call::create_item(collection_id, _owner, _properties)) => { + let collection = >::get(collection_id)?; // sponsor timeout let block_number = >::block_number() as T::BlockNumber; @@ -2583,12 +2630,10 @@ let last_tx_block = >::get((collection_id, &who)); let limit_time = last_tx_block + limit.into(); if block_number <= limit_time { - sponsored = false; + return None; } } - if sponsored { - >::insert((collection_id, who.clone()), block_number); - } + >::insert((collection_id, who.clone()), block_number); // check free create limit if (collection.limits.sponsored_data_size >= (_properties.len() as u32)) && @@ -2596,18 +2641,18 @@ { collection.sponsorship.sponsor() .cloned() - .unwrap_or_default() } else { - T::AccountId::default() + None } } Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => { + let collection = >::get(collection_id)?; let mut sponsor_transfer = false; - if >::get(collection_id).sponsorship.confirmed() { + if collection.sponsorship.confirmed() { - let collection_limits = >::get(collection_id).limits; - let collection_mode = >::get(collection_id).mode; + let collection_limits = collection.limits; + let collection_mode = collection.mode; // sponsor timeout let block_number = >::block_number() as T::BlockNumber; @@ -2689,19 +2734,68 @@ } if !sponsor_transfer { - T::AccountId::default() + None } else { - >::get(collection_id).sponsorship.sponsor() + collection.sponsorship.sponsor() .cloned() - .unwrap_or_default() } } - _ => T::AccountId::default(), - }; + Some(Call::set_variable_meta_data(collection_id, item_id, data)) => { + let mut sponsor_metadata_changes = false; + + let collection = >::get(collection_id)?; + + if + collection.sponsor_confirmed && + // Can't sponsor fungible collection, this tx will be rejected + // as invalid + !matches!(collection.mode, CollectionMode::Fungible(_)) && + data.len() <= collection.limits.sponsored_data_size as usize + { + if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit { + let block_number = >::block_number() as T::BlockNumber; + + if >::get(collection_id, item_id) + .map(|last_block| block_number - last_block > rate_limit) + .unwrap_or(true) + { + sponsor_metadata_changes = true; + >::insert(collection_id, item_id, block_number); + } + } + } + + if !sponsor_metadata_changes { + None + } else { + Some(collection.sponsor) + } + } + + _ => None, + })(); + + match IsSubType::>::is_sub_type(call) { + Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => { + let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default()); + + let owned_contract = >::contains_key(called_contract.clone()) + && >::get(called_contract.clone()) == *who; + let white_list_enabled = >::contains_key(called_contract.clone()) && >::get(called_contract.clone()); + + if !owned_contract && white_list_enabled { + if !>::contains_key(called_contract.clone(), who) { + return Err(InvalidTransaction::Call.into()); + } + } + }, + _ => {}, + } + // Sponsor smart contracts - sponsor = match IsSubType::>::is_sub_type(call) { + sponsor = sponsor.or_else(|| match IsSubType::>::is_sub_type(call) { // On instantiation: set the contract owner Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => { @@ -2713,7 +2807,7 @@ ); >::insert(new_contract_address.clone(), who.clone()); - T::AccountId::default() + None }, // On instantiation with code: set the contract owner @@ -2727,23 +2821,13 @@ >::insert(new_contract_address.clone(), who.clone()); - T::AccountId::default() + None } // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => { let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default()); - - let owned_contract = >::contains_key(called_contract.clone()) - && >::get(called_contract.clone()) == *who; - let white_list_enabled = >::contains_key(called_contract.clone()) && >::get(called_contract.clone()); - - if !owned_contract && white_list_enabled { - if !>::contains_key(called_contract.clone(), who) { - return Err(InvalidTransaction::Call.into()); - } - } let mut sponsor_transfer = false; if >::contains_key(called_contract.clone()) { @@ -2760,26 +2844,21 @@ sponsor_transfer = false; } - - let mut sp = T::AccountId::default(); if sponsor_transfer { if >::contains_key(called_contract.clone()) { if >::get(called_contract.clone()) { - sp = called_contract; + return Some(called_contract); } } } - sp + None }, - _ => sponsor, - }; + _ => None, + }); - let mut who_pays_fee: T::AccountId = sponsor.clone(); - if sponsor == T::AccountId::default() { - who_pays_fee = who.clone(); - } + let who_pays_fee = sponsor.unwrap_or_else(|| who.clone()); <::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction>::withdraw_fee(&who_pays_fee, call, info, fee, tip) .map(|i| (fee, i)) --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -55,11 +55,11 @@ let saved_col_name: Vec = "Test1\0".encode_utf16().collect::>(); let saved_description: Vec = "TestDescription1\0".encode_utf16().collect::>(); let saved_prefix: Vec = b"token_prefix1\0".to_vec(); - assert_eq!(TemplateModule::collection(id).owner, owner); - assert_eq!(TemplateModule::collection(id).name, saved_col_name); - assert_eq!(TemplateModule::collection(id).mode, *mode); - assert_eq!(TemplateModule::collection(id).description, saved_description); - assert_eq!(TemplateModule::collection(id).token_prefix, saved_prefix); + assert_eq!(TemplateModule::collection_id(id).unwrap().owner, owner); + assert_eq!(TemplateModule::collection_id(id).unwrap().name, saved_col_name); + assert_eq!(TemplateModule::collection_id(id).unwrap().mode, *mode); + assert_eq!(TemplateModule::collection_id(id).unwrap().description, saved_description); + assert_eq!(TemplateModule::collection_id(id).unwrap().token_prefix, saved_prefix); id } @@ -89,7 +89,7 @@ let collection_id = create_test_collection(&CollectionMode::NFT, 1); assert_ok!(TemplateModule::set_schema_version(origin1, collection_id, SchemaVersion::Unique)); - assert_eq!(TemplateModule::collection(collection_id).schema_version, SchemaVersion::Unique); + assert_eq!(TemplateModule::collection_id(collection_id).unwrap().schema_version, SchemaVersion::Unique); }); } @@ -622,7 +622,7 @@ collection_id, 2 )); - assert_eq!(TemplateModule::collection(collection_id).owner, 2); + assert_eq!(TemplateModule::collection_id(collection_id).unwrap().owner, 2); }); } @@ -1841,8 +1841,8 @@ let origin1 = Origin::signed(1); assert_ok!(TemplateModule::set_const_on_chain_schema(origin1, collection_id, b"test const on chain schema".to_vec())); - assert_eq!(TemplateModule::collection(collection_id).const_on_chain_schema, b"test const on chain schema".to_vec()); - assert_eq!(TemplateModule::collection(collection_id).variable_on_chain_schema, b"".to_vec()); + assert_eq!(TemplateModule::collection_id(collection_id).unwrap().const_on_chain_schema, b"test const on chain schema".to_vec()); + assert_eq!(TemplateModule::collection_id(collection_id).unwrap().variable_on_chain_schema, b"".to_vec()); }); } @@ -1856,8 +1856,8 @@ let origin1 = Origin::signed(1); assert_ok!(TemplateModule::set_variable_on_chain_schema(origin1, collection_id, b"test variable on chain schema".to_vec())); - assert_eq!(TemplateModule::collection(collection_id).const_on_chain_schema, b"".to_vec()); - assert_eq!(TemplateModule::collection(collection_id).variable_on_chain_schema, b"test variable on chain schema".to_vec()); + assert_eq!(TemplateModule::collection_id(collection_id).unwrap().const_on_chain_schema, b"".to_vec()); + assert_eq!(TemplateModule::collection_id(collection_id).unwrap().variable_on_chain_schema, b"test variable on chain schema".to_vec()); }); } --- a/runtime/src/chain_extension.rs +++ b/runtime/src/chain_extension.rs @@ -60,7 +60,9 @@ } let recipient = AccountId32::from(bytes_rec); - match pallet_nft::Module::::transfer_internal(sender, recipient, input.collection_id, input.token_id, input.amount) { + let collection = pallet_nft::Module::::get_collection(input.collection_id)?; + + match pallet_nft::Module::::transfer_internal(sender, recipient, &collection, input.token_id, input.amount) { Ok(_) => Ok(RetVal::Converging(func_id)), _ => Err(DispatchError::Other("Transfer error")) } --- a/runtime/src/nft_weights.rs +++ b/runtime/src/nft_weights.rs @@ -133,6 +133,11 @@ .saturating_add(DbWeight::get().reads(0 as Weight)) .saturating_add(DbWeight::get().writes(2 as Weight)) } + fn set_variable_meta_data_sponsoring_rate_limit() -> Weight { + (3_500_000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } fn toggle_contract_white_list() -> Weight { (3_000_000 as Weight) .saturating_add(DbWeight::get().reads(0 as Weight)) --- a/runtime_types.json +++ b/runtime_types.json @@ -38,7 +38,7 @@ "Confirmed": "AccountId" } }, - "CollectionType": { + "Collection": { "Owner": "AccountId", "Mode": "CollectionMode", "Access": "AccessMode", @@ -99,7 +99,8 @@ }, "CollectionLimits": { "AccountTokenOwnershipLimit": "u32", - "SponsoredMintSize": "u32", + "SponsoredDataSize": "u32", + "SponsoredDataRateLimit": "Option", "TokenLimit": "u32", "SponsorTimeout": "u32", "OwnerCanTransfer": "bool", --- a/tests/package.json +++ b/tests/package.json @@ -46,7 +46,8 @@ "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts", "testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts", "testSetOffchainSchema": "mocha --timeout 9999999 -r ts-node/register ./**/setOffchainSchema.test.ts", - "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts" + "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts", + "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts" }, "author": "", "license": "SEE LICENSE IN ../LICENSE", --- a/tests/src/addCollectionAdmin.test.ts +++ b/tests/src/addCollectionAdmin.test.ts @@ -1,135 +1,135 @@ -// -// This file is subject to the terms and conditions defined in -// file 'LICENSE', which is part of this source code package. -// - -import { ApiPromise } from '@polkadot/api'; -import BN from 'bn.js'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from './substrate/privateKey'; -import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api'; -import {createCollectionExpectSuccess, destroyCollectionExpectSuccess} from './util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => { - it('Add collection admin.', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const alice = privateKey('//Alice'); - const bob = privateKey('//Bob'); - - const collection: any = (await api.query.nft.collection(collectionId)); - expect(collection.Owner.toString()).to.be.eq(alice.address); - - const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address); - await submitTransactionAsync(alice, changeAdminTx); - - const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); - expect(adminListAfterAddAdmin).to.be.contains(bob.address); - }); - }); - - it('Add admin using added collection admin.', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const Alice = privateKey('//Alice'); - const Bob = privateKey('//Bob'); - const Charlie = privateKey('//CHARLIE'); - - const collection: any = (await api.query.nft.collection(collectionId)); - expect(collection.Owner.toString()).to.be.eq(Alice.address); - - const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address); - await submitTransactionAsync(Alice, changeAdminTx); - - const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); - expect(adminListAfterAddAdmin).to.be.contains(Bob.address); - - const changeAdminTxCharlie = api.tx.nft.addCollectionAdmin(collectionId, Charlie.address); - await submitTransactionAsync(Bob, changeAdminTxCharlie); - const adminListAfterAddNewAdmin: any = (await api.query.nft.adminList(collectionId)); - expect(adminListAfterAddNewAdmin).to.be.contains(Bob.address); - expect(adminListAfterAddNewAdmin).to.be.contains(Charlie.address); - }); - }); -}); - -describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => { - it("Not owner can't add collection admin.", async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const alice = privateKey('//Alice'); - const nonOwner = privateKey('//Bob_stash'); - - const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, alice.address); - await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected; - - const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); - expect(adminListAfterAddAdmin).not.to.be.contains(alice.address); - - // Verifying that nothing bad happened (network is live, new collections can be created, etc.) - await createCollectionExpectSuccess(); - }); - }); - it("Can't add collection admin of not existing collection.", async () => { - await usingApi(async (api) => { - // tslint:disable-next-line: no-bitwise - const collectionId = (1 << 32) - 1; - const alice = privateKey('//Alice'); - const bob = privateKey('//Bob'); - - const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address); - await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected; - - // Verifying that nothing bad happened (network is live, new collections can be created, etc.) - await createCollectionExpectSuccess(); - }); - }); - - it("Can't add an admin to a destroyed collection.", async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const Alice = privateKey('//Alice'); - const Bob = privateKey('//Bob'); - await destroyCollectionExpectSuccess(collectionId); - const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address); - await expect(submitTransactionExpectFailAsync(Alice, changeOwnerTx)).to.be.rejected; - - // Verifying that nothing bad happened (network is live, new collections can be created, etc.) - await createCollectionExpectSuccess(); - }); - }); - - it('Add an admin to a collection that has reached the maximum number of admins limit', async () => { - await usingApi(async (api: ApiPromise) => { - const Alice = privateKey('//Alice'); - const accounts = [ - 'GsvVmjr1CBHwQHw84pPHMDxgNY3iBLz6Qn7qS3CH8qPhrHz', - 'FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP', - 'JKspFU6ohf1Grg3Phdzj2pSgWvsYWzSfKghhfzMbdhNBWs5', - 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P', - 'DfnTB4z7eUvYRqcGtTpFsLC69o6tvBSC1pEv8vWPZFtCkaK', - 'HnMAUz7r2G8G3hB27SYNyit5aJmh2a5P4eMdDtACtMFDbam', - 'DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM', - ]; - const collectionId = await createCollectionExpectSuccess(); - - const chainLimit = await api.query.nft.chainLimit() as unknown as { CollectionAdminsLimit: BN }; - const chainAdminLimit = chainLimit.CollectionAdminsLimit.toNumber(); - expect(chainAdminLimit).to.be.equal(5); - - for (let i = 0; i < chainAdminLimit; i++) { - const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, accounts[i]); - await submitTransactionAsync(Alice, changeAdminTx); - const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); - expect(adminListAfterAddAdmin).to.be.contains(accounts[i]); - } - - const tx = api.tx.nft.addCollectionAdmin(collectionId, accounts[chainAdminLimit]); - await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected; - }); - }); -}); +// +// This file is subject to the terms and conditions defined in +// file 'LICENSE', which is part of this source code package. +// + +import { ApiPromise } from '@polkadot/api'; +import BN from 'bn.js'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from './substrate/privateKey'; +import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api'; +import {createCollectionExpectSuccess, destroyCollectionExpectSuccess} from './util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => { + it('Add collection admin.', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); + expect(collection.Owner.toString()).to.be.eq(alice.address); + + const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address); + await submitTransactionAsync(alice, changeAdminTx); + + const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); + expect(adminListAfterAddAdmin).to.be.contains(bob.address); + }); + }); + + it('Add admin using added collection admin.', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const Alice = privateKey('//Alice'); + const Bob = privateKey('//Bob'); + const Charlie = privateKey('//CHARLIE'); + + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); + expect(collection.Owner.toString()).to.be.eq(Alice.address); + + const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address); + await submitTransactionAsync(Alice, changeAdminTx); + + const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); + expect(adminListAfterAddAdmin).to.be.contains(Bob.address); + + const changeAdminTxCharlie = api.tx.nft.addCollectionAdmin(collectionId, Charlie.address); + await submitTransactionAsync(Bob, changeAdminTxCharlie); + const adminListAfterAddNewAdmin: any = (await api.query.nft.adminList(collectionId)); + expect(adminListAfterAddNewAdmin).to.be.contains(Bob.address); + expect(adminListAfterAddNewAdmin).to.be.contains(Charlie.address); + }); + }); +}); + +describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => { + it("Not owner can't add collection admin.", async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const alice = privateKey('//Alice'); + const nonOwner = privateKey('//Bob_stash'); + + const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, alice.address); + await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected; + + const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); + expect(adminListAfterAddAdmin).not.to.be.contains(alice.address); + + // Verifying that nothing bad happened (network is live, new collections can be created, etc.) + await createCollectionExpectSuccess(); + }); + }); + it("Can't add collection admin of not existing collection.", async () => { + await usingApi(async (api) => { + // tslint:disable-next-line: no-bitwise + const collectionId = (1 << 32) - 1; + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + + const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address); + await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected; + + // Verifying that nothing bad happened (network is live, new collections can be created, etc.) + await createCollectionExpectSuccess(); + }); + }); + + it("Can't add an admin to a destroyed collection.", async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const Alice = privateKey('//Alice'); + const Bob = privateKey('//Bob'); + await destroyCollectionExpectSuccess(collectionId); + const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address); + await expect(submitTransactionExpectFailAsync(Alice, changeOwnerTx)).to.be.rejected; + + // Verifying that nothing bad happened (network is live, new collections can be created, etc.) + await createCollectionExpectSuccess(); + }); + }); + + it('Add an admin to a collection that has reached the maximum number of admins limit', async () => { + await usingApi(async (api: ApiPromise) => { + const Alice = privateKey('//Alice'); + const accounts = [ + 'GsvVmjr1CBHwQHw84pPHMDxgNY3iBLz6Qn7qS3CH8qPhrHz', + 'FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP', + 'JKspFU6ohf1Grg3Phdzj2pSgWvsYWzSfKghhfzMbdhNBWs5', + 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P', + 'DfnTB4z7eUvYRqcGtTpFsLC69o6tvBSC1pEv8vWPZFtCkaK', + 'HnMAUz7r2G8G3hB27SYNyit5aJmh2a5P4eMdDtACtMFDbam', + 'DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM', + ]; + const collectionId = await createCollectionExpectSuccess(); + + const chainLimit = await api.query.nft.chainLimit() as unknown as { CollectionAdminsLimit: BN }; + const chainAdminLimit = chainLimit.CollectionAdminsLimit.toNumber(); + expect(chainAdminLimit).to.be.equal(5); + + for (let i = 0; i < chainAdminLimit; i++) { + const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, accounts[i]); + await submitTransactionAsync(Alice, changeAdminTx); + const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId)); + expect(adminListAfterAddAdmin).to.be.contains(accounts[i]); + } + + const tx = api.tx.nft.addCollectionAdmin(collectionId, accounts[chainAdminLimit]); + await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected; + }); + }); +}); --- a/tests/src/change-collection-owner.test.ts +++ b/tests/src/change-collection-owner.test.ts @@ -19,13 +19,13 @@ const alice = privateKey('//Alice'); const bob = privateKey('//Bob'); - const collection: any = (await api.query.nft.collection(collectionId)); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collection.Owner.toString()).to.be.eq(alice.address); const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address); await submitTransactionAsync(alice, changeOwnerTx); - const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId)); + const collectionAfterOwnerChange: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(bob.address); }); }); @@ -41,7 +41,7 @@ const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address); await expect(submitTransactionExpectFailAsync(bob, changeOwnerTx)).to.be.rejected; - const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId)); + const collectionAfterOwnerChange: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(alice.address); // Verifying that nothing bad happened (network is live, new collections can be created, etc.) --- a/tests/src/removeCollectionAdmin.test.ts +++ b/tests/src/removeCollectionAdmin.test.ts @@ -19,7 +19,7 @@ const collectionId = await createCollectionExpectSuccess(); const Alice = privateKey('//Alice'); const Bob = privateKey('//Bob'); - const collection: any = (await api.query.nft.collection(collectionId)); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collection.Owner.toString()).to.be.eq(Alice.address); // first - add collection admin Bob const addAdminTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address); --- a/tests/src/setCollectionLimits.test.ts +++ b/tests/src/setCollectionLimits.test.ts @@ -61,12 +61,12 @@ // tslint:disable-next-line:no-unused-expression expect(result.success).to.be.true; - expect(collectionInfo.Limits.AccountTokenOwnershipLimit.toNumber()).to.be.equal(accountTokenOwnershipLimit); - expect(collectionInfo.Limits.SponsoredMintSize.toNumber()).to.be.equal(sponsoredDataSize); - expect(collectionInfo.Limits.TokenLimit.toNumber()).to.be.equal(tokenLimit); - expect(collectionInfo.Limits.SponsorTimeout.toNumber()).to.be.equal(sponsorTimeout); - expect(collectionInfo.Limits.OwnerCanTransfer.valueOf()).to.be.true; - expect(collectionInfo.Limits.OwnerCanDestroy.valueOf()).to.be.true; + expect(collectionInfo.Limits.AccountTokenOwnershipLimit).to.be.equal(accountTokenOwnershipLimit); + expect(collectionInfo.Limits.SponsoredDataSize).to.be.equal(sponsoredDataSize); + expect(collectionInfo.Limits.TokenLimit).to.be.equal(tokenLimit); + expect(collectionInfo.Limits.SponsorTimeout).to.be.equal(sponsorTimeout); + expect(collectionInfo.Limits.OwnerCanTransfer).to.be.true; + expect(collectionInfo.Limits.OwnerCanDestroy).to.be.true; }); }); }); --- a/tests/src/setConstOnChainSchema.test.ts +++ b/tests/src/setConstOnChainSchema.test.ts @@ -36,7 +36,7 @@ it('Run extrinsic with parameters of the collection id, set the scheme', async () => { await usingApi(async (api) => { const collectionId = await createCollectionExpectSuccess(); - const collection: any = (await api.query.nft.collection(collectionId)); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collection.Owner.toString()).to.be.eq(Alice.address); const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema); await submitTransactionAsync(Alice, setShema); @@ -48,7 +48,7 @@ const collectionId = await createCollectionExpectSuccess(); const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema); await submitTransactionAsync(Alice, setShema); - const collection: any = (await api.query.nft.collection(collectionId)); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collection.ConstOnChainSchema.toString()).to.be.eq(Shema); }); @@ -86,7 +86,7 @@ it('Execute method not on behalf of the collection owner', async () => { await usingApi(async (api) => { const collectionId = await createCollectionExpectSuccess(); - const collection: any = (await api.query.nft.collection(collectionId)); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); expect(collection.Owner.toString()).to.be.eq(Alice.address); const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema); await expect(submitTransactionExpectFailAsync(Bob, setShema)).to.be.rejected; --- a/tests/src/setOffchainSchema.test.ts +++ b/tests/src/setOffchainSchema.test.ts @@ -36,7 +36,7 @@ await setOffchainSchemaExpectSuccess(alice, collectionId, DATA); const collection = await queryCollectionExpectSuccess(collectionId); - expect(Array.from(collection.OffchainSchema)).to.be.deep.equal(DATA); + expect(collection.OffchainSchema).to.be.equal('0x' + Buffer.from(DATA).toString('hex')); }); }); --- /dev/null +++ b/tests/src/setVariableMetadataSponsoringRateLimit.test.ts @@ -0,0 +1,80 @@ +// +// This file is subject to the terms and conditions defined in +// file 'LICENSE', which is part of this source code package. +// + +import { IKeyringPair } from '@polkadot/types/types'; +import privateKey from './substrate/privateKey'; +import usingApi from './substrate/substrate-api'; +import { + confirmSponsorshipExpectSuccess, + createCollectionExpectSuccess, + createItemExpectSuccess, + findUnusedAddress, + setCollectionLimitsExpectSuccess, + setCollectionSponsorExpectSuccess, + setVariableMetaDataExpectFailure, + setVariableMetaDataExpectSuccess, +} from './util/helpers'; + +describe('Integration Test setVariableMetadataSponsoringRateLimit', () => { + let alice: IKeyringPair; + let userWithNoBalance: IKeyringPair; + + before(async () => { + await usingApi(async (api) => { + alice = privateKey('//Alice'); + userWithNoBalance = await findUnusedAddress(api); + }); + }); + + it('sponsored setVariableMetaData can be called twice with pause for free', async () => { + const collectionId = await createCollectionExpectSuccess(); + await setCollectionSponsorExpectSuccess(collectionId, alice.address); + await confirmSponsorshipExpectSuccess(collectionId); + await setCollectionLimitsExpectSuccess(alice, collectionId, { + SponsoredDataRateLimit: 0, + }); + + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address); + await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1, 2, 3]); + await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1, 2, 3]); + }); + + it('sponsored setVariableMetaData can\'t be called twice without pause for free', async () => { + const collectionId = await createCollectionExpectSuccess(); + await setCollectionSponsorExpectSuccess(collectionId, alice.address); + await confirmSponsorshipExpectSuccess(collectionId); + await setCollectionLimitsExpectSuccess(alice, collectionId, { + SponsoredDataRateLimit: 10, + }); + + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address); + await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1, 2, 3]); + await setVariableMetaDataExpectFailure(userWithNoBalance, collectionId, itemId, [1, 2, 3]); + }); + + it('sponsored setVariableMetaData can\'t be called for free with variable metadata above collection limits', async () => { + const collectionId = await createCollectionExpectSuccess(); + await setCollectionSponsorExpectSuccess(collectionId, alice.address); + await confirmSponsorshipExpectSuccess(collectionId); + await setCollectionLimitsExpectSuccess(alice, collectionId, { + SponsoredDataRateLimit: 0, + SponsoredDataSize: 1, + }); + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address); + + await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1]); + await setVariableMetaDataExpectFailure(userWithNoBalance, collectionId, itemId, [1, 2]); + }); + + it.only('Default value of rate limit does not sponsor setting variable metadata', async () => { + const collectionId = await createCollectionExpectSuccess(); + await setCollectionSponsorExpectSuccess(collectionId, alice.address); + await confirmSponsorshipExpectSuccess(collectionId); + + const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address); + await setVariableMetaDataExpectFailure(userWithNoBalance, collectionId, itemId, [1]); + }); + +}); --- a/tests/src/setVariableOnChainSchema.test.ts +++ b/tests/src/setVariableOnChainSchema.test.ts @@ -1,96 +1,96 @@ -// -// This file is subject to the terms and conditions defined in -// file 'LICENSE', which is part of this source code package. -// - -import { Keyring } from '@polkadot/api'; -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api'; -import { - createCollectionExpectSuccess, - destroyCollectionExpectSuccess, -} from './util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Schema: any; -let largeSchema: any; - -before(async () => { - await usingApi(async (api) => { - const keyring = new Keyring({ type: 'sr25519' }); - Alice = keyring.addFromUri('//Alice'); - Bob = keyring.addFromUri('//Bob'); - Schema = '0x31'; - largeSchema = new Array(4097).fill(0xff); - - }); -}); -describe('Integration Test ext. setVariableOnChainSchema()', () => { - - it('Run extrinsic with parameters of the collection id, set the scheme', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const collection: any = (await api.query.nft.collection(collectionId)); - expect(collection.Owner.toString()).to.be.eq(Alice.address); - const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); - await submitTransactionAsync(Alice, setSchema); - }); - }); - - it('Checking collection data using the setVariableOnChainSchema parameter', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); - await submitTransactionAsync(Alice, setSchema); - const collection: any = (await api.query.nft.collection(collectionId)); - expect(collection.VariableOnChainSchema.toString()).to.be.eq(Schema); - - }); - }); -}); - -describe('Negative Integration Test ext. setVariableOnChainSchema()', () => { - - it('Set a non-existent collection', async () => { - await usingApi(async (api) => { - // tslint:disable-next-line: radix - const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1; - const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); - await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected; - }); - }); - - it('Set a previously deleted collection', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - await destroyCollectionExpectSuccess(collectionId); - const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); - await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected; - }); - }); - - it('Set invalid data in schema (size too large:> 1024b)', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, largeSchema); - await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected; - }); - }); - - it('Execute method not on behalf of the collection owner', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const collection: any = (await api.query.nft.collection(collectionId)); - expect(collection.Owner.toString()).to.be.eq(Alice.address); - const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); - await expect(submitTransactionExpectFailAsync(Bob, setSchema)).to.be.rejected; - }); - }); - -}); +// +// This file is subject to the terms and conditions defined in +// file 'LICENSE', which is part of this source code package. +// + +import { Keyring } from '@polkadot/api'; +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api'; +import { + createCollectionExpectSuccess, + destroyCollectionExpectSuccess, +} from './util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Schema: any; +let largeSchema: any; + +before(async () => { + await usingApi(async (api) => { + const keyring = new Keyring({ type: 'sr25519' }); + Alice = keyring.addFromUri('//Alice'); + Bob = keyring.addFromUri('//Bob'); + Schema = '0x31'; + largeSchema = new Array(4097).fill(0xff); + + }); +}); +describe('Integration Test ext. setVariableOnChainSchema()', () => { + + it('Run extrinsic with parameters of the collection id, set the scheme', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); + expect(collection.Owner.toString()).to.be.eq(Alice.address); + const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); + await submitTransactionAsync(Alice, setSchema); + }); + }); + + it('Checking collection data using the setVariableOnChainSchema parameter', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); + await submitTransactionAsync(Alice, setSchema); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); + expect(collection.VariableOnChainSchema.toString()).to.be.eq(Schema); + + }); + }); +}); + +describe('Negative Integration Test ext. setVariableOnChainSchema()', () => { + + it('Set a non-existent collection', async () => { + await usingApi(async (api) => { + // tslint:disable-next-line: radix + const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1; + const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); + await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected; + }); + }); + + it('Set a previously deleted collection', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + await destroyCollectionExpectSuccess(collectionId); + const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); + await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected; + }); + }); + + it('Set invalid data in schema (size too large:> 1024b)', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, largeSchema); + await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected; + }); + }); + + it('Execute method not on behalf of the collection owner', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); + expect(collection.Owner.toString()).to.be.eq(Alice.address); + const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema); + await expect(submitTransactionExpectFailAsync(Bob, setSchema)).to.be.rejected; + }); + }); + +}); --- a/tests/src/types.ts +++ b/tests/src/types.ts @@ -13,10 +13,11 @@ Description: [BN, BN]; // utf16 isReFungible: boolean; Limits: { - AccountTokenOwnershipLimit: BN; - SponsoredMintSize: BN; - TokenLimit: BN; - SponsorTimeout: BN; + AccountTokenOwnershipLimit: number; + SponsoredDataSize: number; + SponsoredDataRateLimit?: number, + TokenLimit: number; + SponsorTimeout: number; OwnerCanTransfer: boolean; OwnerCanDestroy: boolean; }; --- a/tests/src/util/contracthelpers.ts +++ b/tests/src/util/contracthelpers.ts @@ -22,9 +22,9 @@ function deployContract(alice: IKeyringPair, code: CodePromise, constructor: string = 'default', ...args: any[]): Promise { return new Promise(async (resolve, reject) => { - const unsub = await code + const unsub = await (code as any) .tx[constructor]({value: endowment, gasLimit}, ...args) - .signAndSend(alice, (result) => { + .signAndSend(alice, (result: any) => { if (result.status.isInBlock || result.status.isFinalized) { // here we have an additional field in the result, containing the blueprint resolve((result as any).contract); --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -212,7 +212,7 @@ const BcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString(), 10); // Get the collection - const collection: any = (await api.query.nft.collection(result.collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(result.collectionId)).toJSON(); // What to expect // tslint:disable-next-line:no-unused-expression @@ -324,18 +324,17 @@ const result = getDestroyResult(events); // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); // What to expect expect(result).to.be.true; - expect(collection).to.be.not.null; - expect(collection.Owner).to.be.equal(nullPublicKey); + expect(collection).to.be.null; }); } export async function queryCollectionLimits(collectionId: number) { return await usingApi(async (api) => { - return ((await api.query.nft.collection(collectionId)).toJSON() as any).Limits; + return ((await api.query.nft.collectionById(collectionId)).toJSON() as any).Limits; }); } @@ -373,7 +372,7 @@ const result = getGenericResult(events); // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); // What to expect expect(result.success).to.be.true; @@ -393,7 +392,7 @@ const result = getGenericResult(events); // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); // What to expect expect(result.success).to.be.true; @@ -431,7 +430,7 @@ const result = getGenericResult(events); // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); // What to expect expect(result.success).to.be.true; @@ -839,7 +838,7 @@ const result = getGenericResult(events); // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); // What to expect // tslint:disable-next-line:no-unused-expression @@ -865,7 +864,7 @@ const result = getGenericResult(events); // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON(); // What to expect // tslint:disable-next-line:no-unused-expression @@ -947,7 +946,7 @@ export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number) : Promise => { - return await api.query.nft.collection(collectionId) as unknown as ICollectionInterface; + return (await api.query.nft.collectionById(collectionId)).toJSON() as unknown as ICollectionInterface; }; export const getCreatedCollectionCount = async (api: ApiPromise): Promise => { @@ -957,6 +956,6 @@ export async function queryCollectionExpectSuccess(collectionId: number): Promise { return await usingApi(async (api) => { - return (await api.query.nft.collection(collectionId)) as unknown as ICollectionInterface; + return (await api.query.nft.collectionById(collectionId)).toJSON() as unknown as ICollectionInterface; }); }