difftreelog
Merge pull request #197 from UniqueNetwork/feature/CORE-167
in: master
Limits logic fixed. Tests added
5 files changed
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -306,9 +306,6 @@
/// Amount of collections destroyed, used for total amount tracking with
/// CreatedCollectionCount
DestroyedCollectionCount: u32;
- /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)
- /// Account id (real)
- pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;
//#endregion
//#region Basic collections
@@ -1125,6 +1122,7 @@
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = Self::get_collection(collection_id)?;
+ Self::meta_update_check(&sender, &collection, item_id)?;
Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;
@@ -1647,11 +1645,19 @@
collection.consume_sload()?;
let account_items: u32 =
<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;
- ensure!(
- collection.limits.account_token_ownership_limit > account_items,
- Error::<T>::AccountTokenLimitExceeded
- );
+ // zero limit means collection limit is disabled
+ // otherwise get lower value
+ let limit = if collection.limits.account_token_ownership_limit == 0
+ || collection.limits.account_token_ownership_limit > ACCOUNT_TOKEN_OWNERSHIP_LIMIT
+ {
+ ACCOUNT_TOKEN_OWNERSHIP_LIMIT
+ } else {
+ collection.limits.account_token_ownership_limit
+ };
+
+ ensure!(limit > account_items, Error::<T>::AccountTokenLimitExceeded);
+
// preliminary transfer check
ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);
@@ -1674,12 +1680,23 @@
as u32)
.checked_add(amount)
.ok_or(Error::<T>::AccountTokenLimitExceeded)?;
+
+ // zero limit means collection limit is disabled
+ // otherwise get lower value
+ let account_token_limit = if collection.limits.account_token_ownership_limit == 0
+ || collection.limits.account_token_ownership_limit > ACCOUNT_TOKEN_OWNERSHIP_LIMIT
+ {
+ ACCOUNT_TOKEN_OWNERSHIP_LIMIT
+ } else {
+ collection.limits.account_token_ownership_limit
+ };
+
ensure!(
collection.limits.token_limit >= total_items,
Error::<T>::CollectionTokenLimitExceeded
);
ensure!(
- collection.limits.account_token_ownership_limit >= account_items,
+ account_token_limit >= account_items,
Error::<T>::AccountTokenLimitExceeded
);
@@ -2409,32 +2426,19 @@
item_index: TokenId,
owner: &T::CrossAccountId,
) -> DispatchResult {
- // add to account limit
collection.consume_sload()?;
- if <AccountItemCount<T>>::contains_key(owner.as_sub()) {
- // bound Owned tokens by a single address
+ let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());
+ if list_exists {
collection.consume_sload()?;
- let count = <AccountItemCount<T>>::get(owner.as_sub());
+ let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());
+
+ // bound Owned tokens by a single address in collection
+ let account_items: u32 = list.len() as u32;
ensure!(
- count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,
+ account_items < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,
Error::<T>::AddressOwnershipLimitExceeded
);
- collection.consume_sstore()?;
- <AccountItemCount<T>>::insert(
- owner.as_sub(),
- count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,
- );
- } else {
- collection.consume_sstore()?;
- <AccountItemCount<T>>::insert(owner.as_sub(), 1);
- }
-
- collection.consume_sload()?;
- let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());
- if list_exists {
- collection.consume_sload()?;
- let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());
let item_contains = list.contains(&item_index.clone());
if !item_contains {
@@ -2457,16 +2461,6 @@
item_index: TokenId,
owner: &T::CrossAccountId,
) -> DispatchResult {
- // update counter
- collection.consume_sload()?;
- collection.consume_sstore()?;
- <AccountItemCount<T>>::insert(
- owner.as_sub(),
- <AccountItemCount<T>>::get(owner.as_sub())
- .checked_sub(1)
- .ok_or(Error::<T>::NumOverflow)?,
- );
-
collection.consume_sload()?;
let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());
if list_exists {
pallets/nft/src/sponsorship.rsdiffbeforeafterboth--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -61,10 +61,15 @@
sponsor_transfer = match collection_mode {
CollectionMode::NFT => {
// get correct limit
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
+ let limit: u32 = if collection_limits.sponsor_transfer_timeout != 0 {
+ if collection_limits.sponsor_transfer_timeout > NFT_SPONSOR_TRANSFER_TIMEOUT
+ {
+ collection_limits.sponsor_transfer_timeout
+ } else {
+ NFT_SPONSOR_TRANSFER_TIMEOUT
+ }
} else {
- NFT_SPONSOR_TRANSFER_TIMEOUT
+ 0
};
let mut sponsored = true;
@@ -83,13 +88,18 @@
}
CollectionMode::Fungible(_) => {
// get correct limit
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
+ let limit: u32 = if collection_limits.sponsor_transfer_timeout != 0 {
+ if collection_limits.sponsor_transfer_timeout
+ > FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
+ {
+ collection_limits.sponsor_transfer_timeout
+ } else {
+ FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
+ }
} else {
- FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
+ 0
};
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
let mut sponsored = true;
if FungibleTransferBasket::<T>::contains_key(collection_id, who) {
let last_tx_block = FungibleTransferBasket::<T>::get(collection_id, who);
@@ -106,10 +116,16 @@
}
CollectionMode::ReFungible => {
// get correct limit
- let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
- collection_limits.sponsor_transfer_timeout
+ let limit: u32 = if collection_limits.sponsor_transfer_timeout != 0 {
+ if collection_limits.sponsor_transfer_timeout
+ > REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
+ {
+ collection_limits.sponsor_transfer_timeout
+ } else {
+ REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
+ }
} else {
- REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
+ 0
};
let mut sponsored = true;
pallets/nft/src/tests.rsdiffbeforeafterboth1789 let data = default_nft_data();1789 let data = default_nft_data();1790 assert_noop!(1790 assert_noop!(1791 TemplateModule::create_item(origin1, 1, account(1), data.into()),1791 TemplateModule::create_item(origin1, 1, account(1), data.into()),1792 Error::<Test>::AddressOwnershipLimitExceeded1792 Error::<Test>::AccountTokenLimitExceeded1793 );1793 );1794 });1794 });1795}1795}2016 let data = default_nft_data();2016 let data = default_nft_data();2017 create_test_item(1, &data.into());2017 create_test_item(1, &data.into());201820182019 TemplateModule::set_meta_update_permission_flag(2019 assert_ok!(TemplateModule::set_meta_update_permission_flag(2020 origin1.clone(),2020 origin1.clone(),2021 collection_id,2021 collection_id,2022 MetaUpdatePermission::ItemOwner,2022 MetaUpdatePermission::ItemOwner,2023 );2023 ));202420242025 let variable_data = b"ten chars.".to_vec();2025 let variable_data = b"ten chars.".to_vec();2026 assert_ok!(TemplateModule::set_variable_meta_data(2026 assert_ok!(TemplateModule::set_variable_meta_data(2040}2040}204120412042#[test]2042#[test]2043fn set_variable_meta_data_on_nft_with_item_owner_permission_flag_neg() {2043fn set_variable_meta_data_on_nft_with_item_owner_permission_flag_neg() {2044 new_test_ext().execute_with(|| {2044 new_test_ext().execute_with(|| {2045 // default_limits();2045 let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 1, 1);204620462047 let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);2047 let origin1 = Origin::signed(1);204820482049 let origin1 = Origin::signed(1);2049 assert_ok!(TemplateModule::set_mint_permission(2050 let origin2 = Origin::signed(2);2050 origin1.clone(),20512051 collection_id,2052 assert_ok!(TemplateModule::set_mint_permission(2052 true2053 origin2.clone(),2053 ));2054 collection_id,2054 assert_ok!(TemplateModule::add_to_white_list(2055 true2055 origin1.clone(),2056 ));2056 collection_id,2057 assert_ok!(TemplateModule::add_to_white_list(2057 account(1)2058 origin2.clone(),2058 ));2059 collection_id,20592060 account(1)2060 let data = default_nft_data();2061 ));2061 create_test_item(1, &data.into());206220622063 let data = default_nft_data();2063 assert_ok!(TemplateModule::set_meta_update_permission_flag(2064 create_test_item(1, &data.into());2064 origin1.clone(),20652065 collection_id,2066 assert_ok!(TemplateModule::set_meta_update_permission_flag(2066 MetaUpdatePermission::ItemOwner,2067 origin2.clone(),2067 ));2068 collection_id,20682069 MetaUpdatePermission::ItemOwner,2069 let variable_data = b"1234567890123".to_vec();2070 ));2070 assert_noop!(20712071 TemplateModule::set_variable_meta_data(2072 let variable_data = b"ten chars.++".to_vec();2072 origin1,2073 assert_noop!(2073 collection_id,2074 TemplateModule::set_variable_meta_data(2074 1,2075 origin2,2075 variable_data.clone()2076 collection_id,2076 ),2077 1,2077 Error::<Test>::TokenVariableDataLimitExceeded2078 variable_data.clone()2078 );2079 ),2079 })2080 Error::<Test>::TokenVariableDataLimitExceeded2080}2081 );208220812083 #[test]2082#[test]2084 fn collection_transfer_flag_works() {2083fn collection_transfer_flag_works() {2141 MetaUpdatePermission::Admin,2140 MetaUpdatePermission::Admin,2142 ));2141 ));214321422144 let variable_data = b"test set_variable_meta_data method.".to_vec();2143 let variable_data = b"test.".to_vec();2145 assert_ok!(TemplateModule::set_variable_meta_data(2144 assert_ok!(TemplateModule::set_variable_meta_data(2146 origin1,2145 origin1,2147 collection_id,2146 collection_id,2188 MetaUpdatePermission::Admin,2187 MetaUpdatePermission::Admin,2189 ));2188 ));219021892191 let variable_data = b"test set_variable_meta_data method.".to_vec();2190 let variable_data = b"test.".to_vec();2192 assert_noop!(2191 assert_noop!(2193 TemplateModule::set_variable_meta_data(2192 TemplateModule::set_variable_meta_data(2194 origin1,2193 origin1,2243 MetaUpdatePermission::None,2242 MetaUpdatePermission::None,2244 ));2243 ));224522442246 let variable_data = b"test set_variable_meta_data method.".to_vec();2245 let variable_data = b"test.".to_vec();2247 assert_noop!(2246 assert_noop!(2248 TemplateModule::set_variable_meta_data(2247 TemplateModule::set_variable_meta_data(2249 origin1.clone(),2248 origin1.clone(),2285 assert_eq!(TemplateModule::address_tokens(1, 1), [1]);2284 assert_eq!(TemplateModule::address_tokens(1, 1), [1]);2286 });2285 });2287 }2286}2288 });2289}22902287tests/src/limits.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/limits.test.ts
@@ -0,0 +1,378 @@
+//
+// 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 {
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+ setCollectionLimitsExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ confirmSponsorshipExpectSuccess,
+ createItemExpectSuccess,
+ createItemExpectFailure,
+ transferExpectSuccess,
+ getFreeBalance,
+ waitNewBlocks,
+} from './util/helpers';
+import { expect } from 'chai';
+
+describe('Number of tokens per address (NFT)', () => {
+ let Alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ });
+ });
+
+ it('Collection limits allow greater number than chain limits, chain limits are enforced', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 20 });
+ for(let i = 0; i < 10; i++){
+ await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ }
+ await createItemExpectFailure(Alice, collectionId, 'NFT');
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+
+ it('Collection limits allow lower number than chain limits, collection limits are enforced', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 1 });
+ await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ await createItemExpectFailure(Alice, collectionId, 'NFT');
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+});
+
+describe('Number of tokens per address (ReFungible)', () => {
+ let Alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ });
+ });
+
+ it('Collection limits allow greater number than chain limits, chain limits are enforced', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 20 });
+ for(let i = 0; i < 10; i++){
+ await createItemExpectSuccess(Alice, collectionId, 'ReFungible');
+ }
+ await createItemExpectFailure(Alice, collectionId, 'ReFungible');
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+
+ it('Collection limits allow lower number than chain limits, collection limits are enforced', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 1 });
+ await createItemExpectSuccess(Alice, collectionId, 'ReFungible');
+ await createItemExpectFailure(Alice, collectionId, 'ReFungible');
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+});
+
+describe('Sponsor timeout (NFT)', () => {
+ let Alice: IKeyringPair;
+ let Bob: IKeyringPair;
+ let Charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ Bob = privateKey('//Bob');
+ Charlie = privateKey('//Charlie');
+ });
+ });
+
+ it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 7 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob);
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 5, fail
+ await waitNewBlocks(5);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);
+ const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);
+
+ // check setting SponsorTimeout = 7, success
+ await waitNewBlocks(2); // 5 + 2
+ await transferExpectSuccess(collectionId, tokenId, Charlie, Bob);
+ const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+
+ it('Collection limits have lower timeout value than chain limits, chain limits are enforced', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 1 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob);
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 1, fail
+ await waitNewBlocks(1);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);
+ const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);
+
+ // check setting SponsorTimeout = 5, success
+ await waitNewBlocks(4);
+ await transferExpectSuccess(collectionId, tokenId, Charlie, Bob);
+ const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+});
+
+describe('Sponsor timeout (Fungible)', () => {
+ let Alice: IKeyringPair;
+ let Bob: IKeyringPair;
+ let Charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ Bob = privateKey('//Bob');
+ Charlie = privateKey('//Charlie');
+ });
+ });
+
+ it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 7 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 10, 'Fungible');
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 5, fail
+ await waitNewBlocks(5);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);
+
+ // check setting SponsorTimeout = 7, success
+ await waitNewBlocks(2); // 5 + 2
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);
+
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+
+ it('Collection limits have lower timeout value than chain limits, chain limits are enforced', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 1 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 10, 'Fungible');
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 1, fail
+ await waitNewBlocks(1);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);
+
+ // check setting SponsorTimeout = 5, success
+ await waitNewBlocks(4);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);
+
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+});
+
+describe('Sponsor timeout (ReFungible)', () => {
+ let Alice: IKeyringPair;
+ let Bob: IKeyringPair;
+ let Charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ Bob = privateKey('//Bob');
+ Charlie = privateKey('//Charlie');
+ });
+ });
+
+ it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 7 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'ReFungible');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 100, 'ReFungible');
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 5, fail
+ await waitNewBlocks(5);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');
+ const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);
+
+ // check setting SponsorTimeout = 7, success
+ await waitNewBlocks(2); // 5 + 2
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');
+ const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+
+ it('Collection limits have lower timeout value than chain limits, chain limits are enforced', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 1 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob);
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 1, fail
+ await waitNewBlocks(1);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);
+ const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);
+
+ // check setting SponsorTimeout = 5, success
+ await waitNewBlocks(4);
+ await transferExpectSuccess(collectionId, tokenId, Charlie, Bob);
+ const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+});
+
+describe('Collection zero limits (NFT)', () => {
+ let Alice: IKeyringPair;
+ let Bob: IKeyringPair;
+ let Charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ Bob = privateKey('//Bob');
+ Charlie = privateKey('//Charlie');
+ });
+ });
+
+ it('Limits have 0 in tokens per address field, the chain limits are applied', async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 0 });
+ for(let i = 0; i < 10; i++){
+ await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ }
+ await createItemExpectFailure(Alice, collectionId, 'NFT');
+ });
+
+ it('Limits have 0 in sponsor timeout, no limits are applied', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 0 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob);
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 0, success with next block
+ await waitNewBlocks(1);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);
+ const aliceBalanceAfterSponsoredTransaction1 = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);
+ });
+});
+
+describe.only('Collection zero limits (Fungible)', () => {
+ let Alice: IKeyringPair;
+ let Bob: IKeyringPair;
+ let Charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ Bob = privateKey('//Bob');
+ Charlie = privateKey('//Charlie');
+ });
+ });
+
+ it('Limits have 0 in sponsor timeout, no limits are applied', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 0 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 10, 'Fungible');
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+
+ // check setting SponsorTimeout = 0, success with next block
+ await waitNewBlocks(1);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');
+ const aliceBalanceAfterSponsoredTransaction1 = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);
+ });
+});
+
+describe.only('Collection zero limits (ReFungible)', () => {
+ let Alice: IKeyringPair;
+ let Bob: IKeyringPair;
+ let Charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ Alice = privateKey('//Alice');
+ Bob = privateKey('//Bob');
+ Charlie = privateKey('//Charlie');
+ });
+ });
+
+ it('Limits have 0 in tokens per address field, the chain limits are applied', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 0 });
+ for(let i = 0; i < 10; i++){
+ await createItemExpectSuccess(Alice, collectionId, 'ReFungible');
+ }
+ await createItemExpectFailure(Alice, collectionId, 'ReFungible');
+ });
+
+ it('Limits have 0 in sponsor timeout, no limits are applied', async () => {
+
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'ReFungible' } });
+ await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 0 });
+ const tokenId = await createItemExpectSuccess(Alice, collectionId, 'ReFungible');
+ await setCollectionSponsorExpectSuccess(collectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Alice');
+ await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 100, 'ReFungible');
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');
+ const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();
+
+ // check setting SponsorTimeout = 0, success with next block
+ await waitNewBlocks(1);
+ await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');
+ const aliceBalanceAfterSponsoredTransaction1 = (await getFreeBalance(Alice)).toNumber();
+ expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);
+ });
+});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -811,6 +811,17 @@
}
export async function
+getFreeBalance(account: IKeyringPair) : Promise<BigNumber>
+{
+ let balance = new BigNumber(0) ;
+ await usingApi(async (api) => {
+ balance = new BigNumber((await api.query.system.account(account.address)).data.free.toString());
+ });
+
+ return balance;
+}
+
+export async function
scheduleTransferExpectSuccess(
collectionId: number,
tokenId: number,
@@ -884,8 +895,11 @@
if (type === 'ReFungible') {
const nftItemData =
(await api.query.nft.reFungibleItemList(collectionId, tokenId)).toJSON() as unknown as IReFungibleTokenDataType;
- expect(nftItemData.Owner[0].Owner).to.be.deep.equal(to);
- expect(nftItemData.Owner[0].Fraction.toString()).to.be.equal(value.toString());
+ const expectedOwner = toSubstrateAddress(to);
+ const ownerIndex = nftItemData.Owner.findIndex(v => toSubstrateAddress(v.Owner as any as string) == expectedOwner);
+ expect(ownerIndex).to.not.equal(-1);
+ expect(nftItemData.Owner[ownerIndex].Owner).to.be.deep.equal(normalizeAccountId(to));
+ expect(nftItemData.Owner[ownerIndex].Fraction).to.be.greaterThanOrEqual(value as number);
}
});
}
@@ -1190,7 +1204,6 @@
export async function waitNewBlocks(blocksCount = 1): Promise<void> {
await usingApi(async (api) => {
const promise = new Promise<void>(async (resolve) => {
-
const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => {
if (blocksCount > 0) {
blocksCount--;