From 836070f4956be12442ff76b575e105d4e53fdc4a Mon Sep 17 00:00:00 2001 From: str-mv Date: Wed, 08 Sep 2021 07:08:22 +0000 Subject: [PATCH] Limits logic fixed. Tests added --- --- 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 = >::get(collection_id, recipient.as_sub()).len() as u32; - ensure!( - collection.limits.account_token_ownership_limit > account_items, - Error::::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::::AccountTokenLimitExceeded); + // preliminary transfer check ensure!(collection.transfers_enabled, Error::::TransferNotAllowed); @@ -1622,12 +1627,23 @@ as u32) .checked_add(amount) .ok_or(Error::::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::::CollectionTokenLimitExceeded ); ensure!( - collection.limits.account_token_ownership_limit >= account_items, + account_token_limit >= account_items, Error::::AccountTokenLimitExceeded ); @@ -2362,32 +2378,19 @@ item_index: TokenId, owner: &T::CrossAccountId, ) -> DispatchResult { - // add to account limit collection.consume_sload()?; - if >::contains_key(owner.as_sub()) { - // bound Owned tokens by a single address + let list_exists = >::contains_key(collection.id, owner.as_sub()); + if list_exists { collection.consume_sload()?; - let count = >::get(owner.as_sub()); + let mut list = >::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::::AddressOwnershipLimitExceeded ); - - collection.consume_sstore()?; - >::insert( - owner.as_sub(), - count.checked_add(1).ok_or(Error::::NumOverflow)?, - ); - } else { - collection.consume_sstore()?; - >::insert(owner.as_sub(), 1); - } - collection.consume_sload()?; - let list_exists = >::contains_key(collection.id, owner.as_sub()); - if list_exists { - collection.consume_sload()?; - let mut list = >::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()?; - >::insert( - owner.as_sub(), - >::get(owner.as_sub()) - .checked_sub(1) - .ok_or(Error::::NumOverflow)?, - ); - collection.consume_sload()?; let list_exists = >::contains_key(collection.id, owner.as_sub()); if list_exists { --- 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 = >::block_number() as T::BlockNumber; let mut sponsored = true; if FungibleTransferBasket::::contains_key(collection_id, who) { let last_tx_block = FungibleTransferBasket::::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; --- /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); + }); +}); --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -797,6 +797,17 @@ } export async function +getFreeBalance(account: IKeyringPair) : Promise +{ + 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 { 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 { + await usingApi(async (api) => { + const promise = new Promise(async (resolve) => { + + const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => { + if (blocksCount > 0) { + blocksCount--; + } else { + unsubscribe(); + resolve(); + } + }); + }); + return promise; + }); +} -- gitstuff