git.delta.rocks / unique-network / refs/commits / 836070f4956b

difftreelog

Limits logic fixed. Tests added

str-mv2021-09-08parent: #abfdac1.patch.diff
in: master

4 files changed

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -302,9 +302,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
@@ -1595,11 +1592,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);
 
@@ -1622,12 +1627,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
 		);
 
@@ -2362,32 +2378,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 {
@@ -2410,16 +2413,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 {
modifiedpallets/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;
addedtests/src/limits.test.tsdiffbeforeafterboth
after · tests/src/limits.test.ts
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56import { IKeyringPair } from '@polkadot/types/types';7import privateKey from './substrate/privateKey';8import usingApi from './substrate/substrate-api';9import {10  createCollectionExpectSuccess,11  destroyCollectionExpectSuccess,12  setCollectionLimitsExpectSuccess,13  setCollectionSponsorExpectSuccess,14  confirmSponsorshipExpectSuccess,15  createItemExpectSuccess,16  createItemExpectFailure,17  transferExpectSuccess,18  getFreeBalance,19  waitNewBlocks,20} from './util/helpers'; 21import { expect } from 'chai';2223describe('Number of tokens per address (NFT)', () => {24  let Alice: IKeyringPair;2526  before(async () => {27    await usingApi(async () => {28      Alice = privateKey('//Alice');29    });30  });3132  it('Collection limits allow greater number than chain limits, chain limits are enforced', async () => {33      34    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });35    await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 20 });36    for(let i = 0; i < 10; i++){37      await createItemExpectSuccess(Alice, collectionId, 'NFT');38    }39    await createItemExpectFailure(Alice, collectionId, 'NFT');40    await destroyCollectionExpectSuccess(collectionId);41  });4243  it('Collection limits allow lower number than chain limits, collection limits are enforced', async () => {4445    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });46    await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 1 });47    await createItemExpectSuccess(Alice, collectionId, 'NFT');48    await createItemExpectFailure(Alice, collectionId, 'NFT');49    await destroyCollectionExpectSuccess(collectionId);50  });51});5253describe('Number of tokens per address (ReFungible)', () => {54  let Alice: IKeyringPair;5556  before(async () => {57    await usingApi(async () => {58      Alice = privateKey('//Alice');59    });60  });6162  it('Collection limits allow greater number than chain limits, chain limits are enforced', async () => {   63    const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});64    await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 20 });65    for(let i = 0; i < 10; i++){66      await createItemExpectSuccess(Alice, collectionId, 'ReFungible');67    }68    await createItemExpectFailure(Alice, collectionId, 'ReFungible');69    await destroyCollectionExpectSuccess(collectionId);70  });7172  it('Collection limits allow lower number than chain limits, collection limits are enforced', async () => {73    const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});74    await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 1 });75    await createItemExpectSuccess(Alice, collectionId, 'ReFungible');76    await createItemExpectFailure(Alice, collectionId, 'ReFungible');77    await destroyCollectionExpectSuccess(collectionId);78  });79});8081describe('Sponsor timeout (NFT)', () => {82  let Alice: IKeyringPair;83  let Bob: IKeyringPair;84  let Charlie: IKeyringPair;8586  before(async () => {87    await usingApi(async () => {88      Alice = privateKey('//Alice');89      Bob = privateKey('//Bob');90      Charlie = privateKey('//Charlie');91    });92  });9394  it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => {  95    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });96    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 7 });97    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');98    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);99    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');100    await transferExpectSuccess(collectionId, tokenId, Alice, Bob);101    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();102103    // check setting SponsorTimeout = 5, fail104    await waitNewBlocks(5);105    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);106    const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();107    expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);108109    // check setting SponsorTimeout = 7, success110    await waitNewBlocks(2); // 5 + 2111    await transferExpectSuccess(collectionId, tokenId, Charlie, Bob);112    const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();113    expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);114    await destroyCollectionExpectSuccess(collectionId);115  });116117  it('Collection limits have lower timeout value than chain limits, chain limits are enforced', async () => {118119    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });120    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 1 });121    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');122    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);123    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');124    await transferExpectSuccess(collectionId, tokenId, Alice, Bob);125    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();126127    // check setting SponsorTimeout = 1, fail128    await waitNewBlocks(1);129    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);130    const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();131    expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);132133    // check setting SponsorTimeout = 5, success134    await waitNewBlocks(4);135    await transferExpectSuccess(collectionId, tokenId, Charlie, Bob);136    const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();137    expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);138    await destroyCollectionExpectSuccess(collectionId);139  });140});141142describe('Sponsor timeout (Fungible)', () => {143  let Alice: IKeyringPair;144  let Bob: IKeyringPair;145  let Charlie: IKeyringPair;146147  before(async () => {148    await usingApi(async () => {149      Alice = privateKey('//Alice');150      Bob = privateKey('//Bob');151      Charlie = privateKey('//Charlie');152    });153  });154155  it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => {  156    const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});157    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 7 });158    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'Fungible');159    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);160    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');161    await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 10, 'Fungible');162    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');163    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();164165    // check setting SponsorTimeout = 5, fail166    await waitNewBlocks(5);167    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');168    const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();169    expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);170171    // check setting SponsorTimeout = 7, success172    await waitNewBlocks(2); // 5 + 2173    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');174    const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();175    expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);176177    await destroyCollectionExpectSuccess(collectionId);178  });179180  it('Collection limits have lower timeout value than chain limits, chain limits are enforced', async () => {181182    const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});183    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 1 });184    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'Fungible');185    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);186    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');187    await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 10, 'Fungible');188    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');189    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();190191    // check setting SponsorTimeout = 1, fail192    await waitNewBlocks(1);193    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');194    const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();195    expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);196197    // check setting SponsorTimeout = 5, success198    await waitNewBlocks(4);199    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');200    const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();201    expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);202203    await destroyCollectionExpectSuccess(collectionId);204  });205});206207describe('Sponsor timeout (ReFungible)', () => {208  let Alice: IKeyringPair;209  let Bob: IKeyringPair;210  let Charlie: IKeyringPair;211212  before(async () => {213    await usingApi(async () => {214      Alice = privateKey('//Alice');215      Bob = privateKey('//Bob');216      Charlie = privateKey('//Charlie');217    });218  });219220  it('Collection limits have greater timeout value than chain limits, collection limits are enforced', async () => {  221    const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});222    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 7 });223    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'ReFungible');224    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);225    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');226    await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 100, 'ReFungible');227    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();228229    // check setting SponsorTimeout = 5, fail230    await waitNewBlocks(5);231    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');232    const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();233    expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);234235    // check setting SponsorTimeout = 7, success236    await waitNewBlocks(2); // 5 + 2237    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');238    const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();239    expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);240    await destroyCollectionExpectSuccess(collectionId);241  });242243  it('Collection limits have lower timeout value than chain limits, chain limits are enforced', async () => {244245    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });246    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 1 });247    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');248    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);249    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');250    await transferExpectSuccess(collectionId, tokenId, Alice, Bob);251    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();252253    // check setting SponsorTimeout = 1, fail254    await waitNewBlocks(1);255    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);256    const aliceBalanceAfterUnsponsoredTransaction = (await getFreeBalance(Alice)).toNumber();257    expect(aliceBalanceAfterUnsponsoredTransaction).to.be.equals(aliceBalanceBefore);258259    // check setting SponsorTimeout = 5, success260    await waitNewBlocks(4);261    await transferExpectSuccess(collectionId, tokenId, Charlie, Bob);262    const aliceBalanceAfterSponsoredTransaction = (await getFreeBalance(Alice)).toNumber();263    expect(aliceBalanceAfterSponsoredTransaction).to.be.lessThan(aliceBalanceBefore);264    await destroyCollectionExpectSuccess(collectionId);265  });266});267268describe('Collection zero limits (NFT)', () => {269  let Alice: IKeyringPair;270  let Bob: IKeyringPair;271  let Charlie: IKeyringPair;272273  before(async () => {274    await usingApi(async () => {275      Alice = privateKey('//Alice');276      Bob = privateKey('//Bob');277      Charlie = privateKey('//Charlie');278    });279  });280281  it('Limits have 0 in tokens per address field, the chain limits are applied', async () => {  282    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });283    await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 0 });284    for(let i = 0; i < 10; i++){285      await createItemExpectSuccess(Alice, collectionId, 'NFT');286    }287    await createItemExpectFailure(Alice, collectionId, 'NFT');288  });289290  it('Limits have 0 in sponsor timeout, no limits are applied', async () => {291292    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });293    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 0 });294    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'NFT');295    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);296    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');297    await transferExpectSuccess(collectionId, tokenId, Alice, Bob);298    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();299300    // check setting SponsorTimeout = 0, success with next block301    await waitNewBlocks(1);302    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie);303    const aliceBalanceAfterSponsoredTransaction1 = (await getFreeBalance(Alice)).toNumber();304    expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);305  });306});307308describe.only('Collection zero limits (Fungible)', () => {309  let Alice: IKeyringPair;310  let Bob: IKeyringPair;311  let Charlie: IKeyringPair;312313  before(async () => {314    await usingApi(async () => {315      Alice = privateKey('//Alice');316      Bob = privateKey('//Bob');317      Charlie = privateKey('//Charlie');318    });319  });320321  it('Limits have 0 in sponsor timeout, no limits are applied', async () => {322    const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});323    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 0 });324    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'Fungible');325    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);326    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');327    await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 10, 'Fungible');328    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();329    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');330331    // check setting SponsorTimeout = 0, success with next block332    await waitNewBlocks(1);333    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 2, 'Fungible');334    const aliceBalanceAfterSponsoredTransaction1 = (await getFreeBalance(Alice)).toNumber();335    expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);336  });337});338339describe.only('Collection zero limits (ReFungible)', () => {340  let Alice: IKeyringPair;341  let Bob: IKeyringPair;342  let Charlie: IKeyringPair;343344  before(async () => {345    await usingApi(async () => {346      Alice = privateKey('//Alice');347      Bob = privateKey('//Bob');348      Charlie = privateKey('//Charlie');349    });350  });351352  it('Limits have 0 in tokens per address field, the chain limits are applied', async () => {  353    const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible' }});354    await setCollectionLimitsExpectSuccess(Alice, collectionId, { AccountTokenOwnershipLimit: 0 });355    for(let i = 0; i < 10; i++){356      await createItemExpectSuccess(Alice, collectionId, 'ReFungible');357    }358    await createItemExpectFailure(Alice, collectionId, 'ReFungible');359  });360361  it('Limits have 0 in sponsor timeout, no limits are applied', async () => {362363    const collectionId = await createCollectionExpectSuccess({ mode: { type: 'ReFungible' } });364    await setCollectionLimitsExpectSuccess(Alice, collectionId, { SponsorTimeout: 0 });365    const tokenId = await createItemExpectSuccess(Alice, collectionId, 'ReFungible');366    await setCollectionSponsorExpectSuccess(collectionId, Alice.address);367    await confirmSponsorshipExpectSuccess(collectionId, '//Alice');368    await transferExpectSuccess(collectionId, tokenId, Alice, Bob, 100, 'ReFungible');369    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');370    const aliceBalanceBefore = (await getFreeBalance(Alice)).toNumber();371372    // check setting SponsorTimeout = 0, success with next block373    await waitNewBlocks(1);374    await transferExpectSuccess(collectionId, tokenId, Bob, Charlie, 20, 'ReFungible');375    const aliceBalanceAfterSponsoredTransaction1 = (await getFreeBalance(Alice)).toNumber();376    expect(aliceBalanceAfterSponsoredTransaction1).to.be.lessThan(aliceBalanceBefore);377  });378});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -797,6 +797,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,
@@ -870,8 +881,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);
     }
   });
 }
@@ -1171,4 +1185,21 @@
 
 export async function queryNftOwner(api: ApiPromise, collectionId: number, tokenId: number): Promise<CrossAccountId> {
   return normalizeAccountId((await api.query.nft.nftItemList(collectionId, tokenId) as any).toJSON().Owner);
-}
\ No newline at end of file
+}
+
+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--;
+        } else {
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+    return promise;
+  });
+}