git.delta.rocks / unique-network / refs/commits / 8bbc6aebf29e

difftreelog

Merge pull request #197 from UniqueNetwork/feature/CORE-167

kozyrevdev2021-10-04parents: #511eef6 #854f33e.patch.diff
in: master
Limits logic fixed. Tests added

5 files changed

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
306 /// Amount of collections destroyed, used for total amount tracking with306 /// Amount of collections destroyed, used for total amount tracking with
307 /// CreatedCollectionCount307 /// CreatedCollectionCount
308 DestroyedCollectionCount: u32;308 DestroyedCollectionCount: u32;
309 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)
310 /// Account id (real)
311 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;
312 //#endregion309 //#endregion
313310
314 //#region Basic collections311 //#region Basic collections
1125 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1122 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
11261123
1127 let collection = Self::get_collection(collection_id)?;1124 let collection = Self::get_collection(collection_id)?;
1125 Self::meta_update_check(&sender, &collection, item_id)?;
11281126
1129 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;1127 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;
11301128
1648 let account_items: u32 =1646 let account_items: u32 =
1649 <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1647 <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;
1648
1649 // zero limit means collection limit is disabled
1650 // otherwise get lower value
1651 let limit = if collection.limits.account_token_ownership_limit == 0
1652 || collection.limits.account_token_ownership_limit > ACCOUNT_TOKEN_OWNERSHIP_LIMIT
1653 {
1654 ACCOUNT_TOKEN_OWNERSHIP_LIMIT
1655 } else {
1656 collection.limits.account_token_ownership_limit
1657 };
1658
1650 ensure!(1659 ensure!(limit > account_items, Error::<T>::AccountTokenLimitExceeded);
1651 collection.limits.account_token_ownership_limit > account_items,
1652 Error::<T>::AccountTokenLimitExceeded
1653 );
16541660
1675 .checked_add(amount)1681 .checked_add(amount)
1676 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1682 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;
1683
1684 // zero limit means collection limit is disabled
1685 // otherwise get lower value
1686 let account_token_limit = if collection.limits.account_token_ownership_limit == 0
1687 || collection.limits.account_token_ownership_limit > ACCOUNT_TOKEN_OWNERSHIP_LIMIT
1688 {
1689 ACCOUNT_TOKEN_OWNERSHIP_LIMIT
1690 } else {
1691 collection.limits.account_token_ownership_limit
1692 };
1693
1677 ensure!(1694 ensure!(
1678 collection.limits.token_limit >= total_items,1695 collection.limits.token_limit >= total_items,
1679 Error::<T>::CollectionTokenLimitExceeded1696 Error::<T>::CollectionTokenLimitExceeded
1680 );1697 );
1681 ensure!(1698 ensure!(
1682 collection.limits.account_token_ownership_limit >= account_items,1699 account_token_limit >= account_items,
1683 Error::<T>::AccountTokenLimitExceeded1700 Error::<T>::AccountTokenLimitExceeded
1684 );1701 );
16851702
2409 item_index: TokenId,2426 item_index: TokenId,
2410 owner: &T::CrossAccountId,2427 owner: &T::CrossAccountId,
2411 ) -> DispatchResult {2428 ) -> DispatchResult {
2412 // add to account limit
2413 collection.consume_sload()?;
2414 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {
2415 // bound Owned tokens by a single address
2416 collection.consume_sload()?;
2417 let count = <AccountItemCount<T>>::get(owner.as_sub());
2418 ensure!(
2419 count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,
2420 Error::<T>::AddressOwnershipLimitExceeded
2421 );
2422
2423 collection.consume_sstore()?;
2424 <AccountItemCount<T>>::insert(
2425 owner.as_sub(),
2426 count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,
2427 );
2428 } else {
2429 collection.consume_sstore()?;
2430 <AccountItemCount<T>>::insert(owner.as_sub(), 1);
2431 }
2432
2433 collection.consume_sload()?;2429 collection.consume_sload()?;
2434 let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2430 let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());
2435 if list_exists {2431 if list_exists {
2436 collection.consume_sload()?;2432 collection.consume_sload()?;
2437 let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2433 let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());
2434
2435 // bound Owned tokens by a single address in collection
2436 let account_items: u32 = list.len() as u32;
2437 ensure!(
2438 account_items < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,
2439 Error::<T>::AddressOwnershipLimitExceeded
2440 );
2441
2438 let item_contains = list.contains(&item_index.clone());2442 let item_contains = list.contains(&item_index.clone());
24392443
2457 item_index: TokenId,2461 item_index: TokenId,
2458 owner: &T::CrossAccountId,2462 owner: &T::CrossAccountId,
2459 ) -> DispatchResult {2463 ) -> DispatchResult {
2460 // update counter
2461 collection.consume_sload()?;
2462 collection.consume_sstore()?;
2463 <AccountItemCount<T>>::insert(
2464 owner.as_sub(),
2465 <AccountItemCount<T>>::get(owner.as_sub())
2466 .checked_sub(1)
2467 .ok_or(Error::<T>::NumOverflow)?,
2468 );
2469
2470 collection.consume_sload()?;2464 collection.consume_sload()?;
2471 let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2465 let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());
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;
modifiedpallets/nft/src/tests.rsdiffbeforeafterboth
--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -1789,7 +1789,7 @@
 		let data = default_nft_data();
 		assert_noop!(
 			TemplateModule::create_item(origin1, 1, account(1), data.into()),
-			Error::<Test>::AddressOwnershipLimitExceeded
+			Error::<Test>::AccountTokenLimitExceeded
 		);
 	});
 }
@@ -2016,11 +2016,11 @@
 		let data = default_nft_data();
 		create_test_item(1, &data.into());
 
-		TemplateModule::set_meta_update_permission_flag(
+		assert_ok!(TemplateModule::set_meta_update_permission_flag(
 			origin1.clone(),
 			collection_id,
 			MetaUpdatePermission::ItemOwner,
-		);
+		));
 
 		let variable_data = b"ten chars.".to_vec();
 		assert_ok!(TemplateModule::set_variable_meta_data(
@@ -2042,20 +2042,17 @@
 #[test]
 fn set_variable_meta_data_on_nft_with_item_owner_permission_flag_neg() {
 	new_test_ext().execute_with(|| {
-		// default_limits();
-
-		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 1, 1);
 
 		let origin1 = Origin::signed(1);
-		let origin2 = Origin::signed(2);
 
 		assert_ok!(TemplateModule::set_mint_permission(
-			origin2.clone(),
+			origin1.clone(),
 			collection_id,
 			true
 		));
 		assert_ok!(TemplateModule::add_to_white_list(
-			origin2.clone(),
+			origin1.clone(),
 			collection_id,
 			account(1)
 		));
@@ -2064,226 +2061,226 @@
 		create_test_item(1, &data.into());
 
 		assert_ok!(TemplateModule::set_meta_update_permission_flag(
-			origin2.clone(),
+			origin1.clone(),
 			collection_id,
 			MetaUpdatePermission::ItemOwner,
 		));
 
-		let variable_data = b"ten chars.++".to_vec();
+		let variable_data = b"1234567890123".to_vec();
 		assert_noop!(
 			TemplateModule::set_variable_meta_data(
-				origin2,
+				origin1,
 				collection_id,
 				1,
 				variable_data.clone()
 			),
 			Error::<Test>::TokenVariableDataLimitExceeded
 		);
+	})
+}
 
-		#[test]
-		fn collection_transfer_flag_works() {
-			new_test_ext().execute_with(|| {
-				let origin1 = Origin::signed(1);
+#[test]
+fn collection_transfer_flag_works() {
+	new_test_ext().execute_with(|| {
+		let origin1 = Origin::signed(1);
 
-				let collection_id = create_test_collection(&CollectionMode::NFT, 1);
-				assert_ok!(TemplateModule::set_transfers_enabled_flag(origin1, 1, true));
+		let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+		assert_ok!(TemplateModule::set_transfers_enabled_flag(origin1, 1, true));
 
-				let data = default_nft_data();
-				create_test_item(collection_id, &data.into());
-				assert_eq!(TemplateModule::balance_count(1, 1), 1);
-				assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+		let data = default_nft_data();
+		create_test_item(collection_id, &data.into());
+		assert_eq!(TemplateModule::balance_count(1, 1), 1);
+		assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
 
-				let origin1 = Origin::signed(1);
+		let origin1 = Origin::signed(1);
 
-				// default scenario
-				assert_ok!(TemplateModule::transfer(origin1, account(2), 1, 1, 1000));
-				assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(2));
-				assert_eq!(TemplateModule::balance_count(1, 1), 0);
-				assert_eq!(TemplateModule::balance_count(1, 2), 1);
+		// default scenario
+		assert_ok!(TemplateModule::transfer(origin1, account(2), 1, 1, 1000));
+		assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(2));
+		assert_eq!(TemplateModule::balance_count(1, 1), 0);
+		assert_eq!(TemplateModule::balance_count(1, 2), 1);
 
-				assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
-			});
-		}
+		assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
+	});
+}
 
-		#[test]
-		fn set_variable_meta_data_on_nft_with_admin_flag() {
-			new_test_ext().execute_with(|| {
-				// default_limits();
+#[test]
+fn set_variable_meta_data_on_nft_with_admin_flag() {
+	new_test_ext().execute_with(|| {
+		// default_limits();
 
-				let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
 
-				let origin1 = Origin::signed(1);
-				let origin2 = Origin::signed(2);
+		let origin1 = Origin::signed(1);
+		let origin2 = Origin::signed(2);
 
-				assert_ok!(TemplateModule::set_mint_permission(
-					origin2.clone(),
-					collection_id,
-					true
-				));
-				assert_ok!(TemplateModule::add_to_white_list(
-					origin2.clone(),
-					collection_id,
-					account(1)
-				));
+		assert_ok!(TemplateModule::set_mint_permission(
+			origin2.clone(),
+			collection_id,
+			true
+		));
+		assert_ok!(TemplateModule::add_to_white_list(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
 
-				assert_ok!(TemplateModule::add_collection_admin(
-					origin2.clone(),
-					collection_id,
-					account(1)
-				));
+		assert_ok!(TemplateModule::add_collection_admin(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
 
-				let data = default_nft_data();
-				create_test_item(1, &data.into());
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
 
-				assert_ok!(TemplateModule::set_meta_update_permission_flag(
-					origin2.clone(),
-					collection_id,
-					MetaUpdatePermission::Admin,
-				));
+		assert_ok!(TemplateModule::set_meta_update_permission_flag(
+			origin2.clone(),
+			collection_id,
+			MetaUpdatePermission::Admin,
+		));
 
-				let variable_data = b"test set_variable_meta_data method.".to_vec();
-				assert_ok!(TemplateModule::set_variable_meta_data(
-					origin1,
-					collection_id,
-					1,
-					variable_data.clone()
-				));
+		let variable_data = b"test.".to_vec();
+		assert_ok!(TemplateModule::set_variable_meta_data(
+			origin1,
+			collection_id,
+			1,
+			variable_data.clone()
+		));
 
-				assert_eq!(
-					TemplateModule::nft_item_id(collection_id, 1)
-						.unwrap()
-						.variable_data,
-					variable_data
-				);
-			});
-		}
+		assert_eq!(
+			TemplateModule::nft_item_id(collection_id, 1)
+				.unwrap()
+				.variable_data,
+			variable_data
+		);
+	});
+}
 
-		#[test]
-		fn set_variable_meta_data_on_nft_with_admin_flag_neg() {
-			new_test_ext().execute_with(|| {
-				// default_limits();
+#[test]
+fn set_variable_meta_data_on_nft_with_admin_flag_neg() {
+	new_test_ext().execute_with(|| {
+		// default_limits();
 
-				let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
 
-				let origin1 = Origin::signed(1);
-				let origin2 = Origin::signed(2);
+		let origin1 = Origin::signed(1);
+		let origin2 = Origin::signed(2);
 
-				assert_ok!(TemplateModule::set_mint_permission(
-					origin2.clone(),
-					collection_id,
-					true
-				));
-				assert_ok!(TemplateModule::add_to_white_list(
-					origin2.clone(),
-					collection_id,
-					account(1)
-				));
+		assert_ok!(TemplateModule::set_mint_permission(
+			origin2.clone(),
+			collection_id,
+			true
+		));
+		assert_ok!(TemplateModule::add_to_white_list(
+			origin2.clone(),
+			collection_id,
+			account(1)
+		));
 
-				let data = default_nft_data();
-				create_test_item(1, &data.into());
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
 
-				assert_ok!(TemplateModule::set_meta_update_permission_flag(
-					origin2.clone(),
-					collection_id,
-					MetaUpdatePermission::Admin,
-				));
+		assert_ok!(TemplateModule::set_meta_update_permission_flag(
+			origin2.clone(),
+			collection_id,
+			MetaUpdatePermission::Admin,
+		));
 
-				let variable_data = b"test set_variable_meta_data method.".to_vec();
-				assert_noop!(
-					TemplateModule::set_variable_meta_data(
-						origin1,
-						collection_id,
-						1,
-						variable_data.clone()
-					),
-					Error::<Test>::NoPermission
-				);
-			});
-		}
+		let variable_data = b"test.".to_vec();
+		assert_noop!(
+			TemplateModule::set_variable_meta_data(
+				origin1,
+				collection_id,
+				1,
+				variable_data.clone()
+			),
+			Error::<Test>::NoPermission
+		);
+	});
+}
 
-		#[test]
-		fn set_variable_meta_flag_after_freeze() {
-			new_test_ext().execute_with(|| {
-				// default_limits();
+#[test]
+fn set_variable_meta_flag_after_freeze() {
+	new_test_ext().execute_with(|| {
+		// default_limits();
 
-				let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
 
-				let origin2 = Origin::signed(2);
+		let origin2 = Origin::signed(2);
 
-				assert_ok!(TemplateModule::set_meta_update_permission_flag(
-					origin2.clone(),
-					collection_id,
-					MetaUpdatePermission::None,
-				));
-				assert_noop!(
-					TemplateModule::set_meta_update_permission_flag(
-						origin2.clone(),
-						collection_id,
-						MetaUpdatePermission::Admin
-					),
-					Error::<Test>::MetadataFlagFrozen
-				);
-			});
-		}
+		assert_ok!(TemplateModule::set_meta_update_permission_flag(
+			origin2.clone(),
+			collection_id,
+			MetaUpdatePermission::None,
+		));
+		assert_noop!(
+			TemplateModule::set_meta_update_permission_flag(
+				origin2.clone(),
+				collection_id,
+				MetaUpdatePermission::Admin
+			),
+			Error::<Test>::MetadataFlagFrozen
+		);
+	});
+}
 
-		#[test]
-		fn set_variable_meta_data_on_nft_with_none_flag_neg() {
-			new_test_ext().execute_with(|| {
-				// default_limits();
+#[test]
+fn set_variable_meta_data_on_nft_with_none_flag_neg() {
+	new_test_ext().execute_with(|| {
+		// default_limits();
 
-				let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 1, 1);
-				let origin1 = Origin::signed(1);
+		let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 1, 1);
+		let origin1 = Origin::signed(1);
 
-				let data = default_nft_data();
-				create_test_item(1, &data.into());
+		let data = default_nft_data();
+		create_test_item(1, &data.into());
 
-				assert_ok!(TemplateModule::set_meta_update_permission_flag(
-					origin1.clone(),
-					collection_id,
-					MetaUpdatePermission::None,
-				));
+		assert_ok!(TemplateModule::set_meta_update_permission_flag(
+			origin1.clone(),
+			collection_id,
+			MetaUpdatePermission::None,
+		));
 
-				let variable_data = b"test set_variable_meta_data method.".to_vec();
-				assert_noop!(
-					TemplateModule::set_variable_meta_data(
-						origin1.clone(),
-						collection_id,
-						1,
-						variable_data.clone()
-					),
-					Error::<Test>::MetadataUpdateDenied
-				);
-			});
-		}
+		let variable_data = b"test.".to_vec();
+		assert_noop!(
+			TemplateModule::set_variable_meta_data(
+				origin1.clone(),
+				collection_id,
+				1,
+				variable_data.clone()
+			),
+			Error::<Test>::MetadataUpdateDenied
+		);
+	});
+}
 
-		#[test]
-		fn collection_transfer_flag_works_neg() {
-			new_test_ext().execute_with(|| {
-				let origin1 = Origin::signed(1);
+#[test]
+fn collection_transfer_flag_works_neg() {
+	new_test_ext().execute_with(|| {
+		let origin1 = Origin::signed(1);
 
-				let collection_id = create_test_collection(&CollectionMode::NFT, 1);
-				assert_ok!(TemplateModule::set_transfers_enabled_flag(
-					origin1, 1, false
-				));
+		let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+		assert_ok!(TemplateModule::set_transfers_enabled_flag(
+			origin1, 1, false
+		));
 
-				let data = default_nft_data();
-				create_test_item(collection_id, &data.into());
-				assert_eq!(TemplateModule::balance_count(1, 1), 1);
-				assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+		let data = default_nft_data();
+		create_test_item(collection_id, &data.into());
+		assert_eq!(TemplateModule::balance_count(1, 1), 1);
+		assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
 
-				let origin1 = Origin::signed(1);
+		let origin1 = Origin::signed(1);
 
-				// default scenario
-				assert_noop!(
-					TemplateModule::transfer(origin1, account(2), 1, 1, 1000),
-					Error::<Test>::TransferNotAllowed
-				);
-				assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(1));
-				assert_eq!(TemplateModule::balance_count(1, 1), 1);
-				assert_eq!(TemplateModule::balance_count(1, 2), 0);
+		// default scenario
+		assert_noop!(
+			TemplateModule::transfer(origin1, account(2), 1, 1, 1000),
+			Error::<Test>::TransferNotAllowed
+		);
+		assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(1));
+		assert_eq!(TemplateModule::balance_count(1, 1), 1);
+		assert_eq!(TemplateModule::balance_count(1, 2), 0);
 
-				assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
-			});
-		}
+		assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
 	});
 }
addedtests/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);
+  });
+});
modifiedtests/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--;