From 8cb94776e05e06fb035f5cc68679abff5992d556 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 24 Jan 2023 04:05:29 +0000 Subject: [PATCH] Refactor the rest of nest.tests --- --- a/tests/src/nesting/nest.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../util'; - -describe('Integration Test: Composite nesting tests', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - const donor = await privateKey({filename: __filename}); - [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); - }); - }); - - /// TODO move - itSub('Performs the full suite: bundles a token, transfers, and unnests', async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); - const targetToken = await collection.mintToken(alice); - - // Create an immediately nested token - const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); - expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - - // Create a token to be nested - const newToken = await collection.mintToken(alice); - - // Nest - await newToken.nest(alice, targetToken); - expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - - // Move bundle to different user - await targetToken.transfer(alice, {Substrate: bob.address}); - expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - - // Unnest - await newToken.unnest(bob, targetToken, {Substrate: bob.address}); - expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); - }); -}); - -describe('Integration Test: Various token type nesting', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let charlie: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - const donor = await privateKey({filename: __filename}); - [alice, bob, charlie] = await helper.arrange.createAccounts([200n, 10n, 10n], donor); - }); - }); - - itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => { - const collectionNFT = await helper.nft.mintCollection(alice, { - permissions: { - nesting: { - tokenOwner: true, - }, - }, - }); - const collectionRFT = await helper.rft.mintCollection(alice); - - const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address}); - const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address}); - - expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address}); - - await rft.transfer(alice, nft.nestingAccount(), 40n); - - expect(await rft.getTopmostOwner()).deep.equal(null); - - await rft.transfer(alice, nft.nestingAccount(), 60n); - - expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address}); - - await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n); - - expect(await rft.getTopmostOwner()).deep.equal(null); - - await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n); - - expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address}); - }); -}); - -describe('Negative Test: Nesting', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - const donor = await privateKey({filename: __filename}); - [alice, bob] = await helper.arrange.createAccounts([100n, 50n], donor); - }); - }); - - itSub('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); - await collection.addAdmin(alice, {Substrate: bob.address}); - const targetToken = await collection.mintToken(alice); - - // Try to create an immediately nested token as collection admin when it's disallowed - await expect(collection.mintToken(bob, targetToken.nestingAccount())) - .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - - // Try to create a token to be nested and nest - const newToken = await collection.mintToken(bob); - await expect(newToken.nest(bob, targetToken)) - .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - - expect(await targetToken.getChildren()).to.be.length(0); - expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); - }); - - itSub('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async ({helper}) => { - const collectionAlice = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}}); - const targetTokenBob = await collectionAlice.mintToken(alice, {Substrate: bob.address}); - await collectionAlice.addToAllowList(alice, {Substrate: bob.address}); - await collectionAlice.addToAllowList(alice, targetTokenBob.nestingAccount()); - - // Try to create a nested token as token owner when it's disallowed - await expect(collectionAlice.mintToken(bob, targetTokenBob.nestingAccount())) - .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - - // Try to create a token to be nested and nest - const newToken = await collectionAlice.mintToken(bob); - await expect(newToken.nest(bob, targetTokenBob)) - .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - - expect(await targetTokenBob.getChildren()).to.be.length(0); - expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); - }); - - itSub('Admin (NFT): disallows an Admin to unnest someone else\'s token', async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {limits: {ownerCanTransfer: true}, permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}}); - //await collection.addAdmin(alice, {Substrate: bob.address}); - const targetToken = await collection.mintToken(alice, {Substrate: bob.address}); - await collection.addToAllowList(alice, {Substrate: bob.address}); - await collection.addToAllowList(alice, targetToken.nestingAccount()); - - // Try to nest somebody else's token - const newToken = await collection.mintToken(bob); - await expect(newToken.nest(alice, targetToken)) - .to.be.rejectedWith(/common\.NoPermission/); - - // Try to unnest a token belonging to someone else as collection admin - const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); - await expect(nestedToken.unnest(alice, targetToken, {Substrate: bob.address})) - .to.be.rejectedWith(/common\.AddressNotInAllowlist/); - - expect(await targetToken.getChildren()).to.be.length(1); - expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - }); - - itSub('Fungible: disallows a non-Owner to unnest someone else\'s token', async ({helper}) => { - const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true}}}); - const collectionFT = await helper.ft.mintCollection(alice); - const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address}); - - // Nest some tokens as Alice into Bob's token - await collectionFT.mint(alice, 5n, targetToken.nestingAccount()); - - // Try to pull it out - await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n)) - .to.be.rejectedWith(/common\.ApprovedValueTooLow/); - expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); - }); - - itSub('Fungible: disallows a non-Owner to unnest someone else\'s token (Restricted nesting)', async ({helper}) => { - const collectionNFT = await helper.nft.mintCollection(alice); - const collectionFT = await helper.ft.mintCollection(alice); - const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address}); - - await collectionNFT.setPermissions(alice, {nesting: {collectionAdmin: true, tokenOwner: true, restricted: [collectionFT.collectionId]}}); - - // Nest some tokens as Alice into Bob's token - await collectionFT.mint(alice, 5n, targetToken.nestingAccount()); - - // Try to pull it out as Alice still - await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n)) - .to.be.rejectedWith(/common\.ApprovedValueTooLow/); - expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); - }); - -}); --- a/tests/src/sub/nesting/admin.test.ts +++ b/tests/src/sub/nesting/admin.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../../util'; +import {expect, itSub, usingPlaygrounds} from '../../util'; describe('Collection admin', () => { let alice: IKeyringPair; --- a/tests/src/sub/nesting/e2e.test.ts +++ b/tests/src/sub/nesting/e2e.test.ts @@ -101,4 +101,35 @@ {tokenId: 0, collectionId: collectionB.collectionId}, ]).and.has.length(1); }); + + /// TODO review this test + itSub('Performs the full suite: bundles a token, transfers, and unnests', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + const targetToken = await collection.mintToken(alice); + + // Create an immediately nested token + const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + + // Create a token to be nested + const newToken = await collection.mintToken(alice); + + // Nest + await newToken.nest(alice, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + + // Move bundle to different user + await targetToken.transfer(alice, {Substrate: bob.address}); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + + // Unnest + await newToken.unnest(bob, targetToken, {Substrate: bob.address}); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); }); --- a/tests/src/sub/nesting/negative.test.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -import {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, Pallets, usingPlaygrounds} from '../../util'; -import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../../util/playgrounds/unique'; -import {itEth} from '../../eth/util'; - -let alice: IKeyringPair; -let bob: IKeyringPair; - -before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - const donor = await privateKey({filename: __filename}); - [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor); - }); -}); - -[ - {mode: 'nft' as const, requiredPallets: []}, - {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, -].map(testCase => { - itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => { - // Create default collection, permissions are not set: - const aliceNFTCollection = await helper.nft.mintCollection(alice); - const targetToken = await aliceNFTCollection.mintToken(alice); - - const collectionForNesting = await helper[testCase.mode].mintCollection(alice); - - // 1. Alice cannot create immediately nested tokens: - const nestingTx = testCase.mode === 'nft' - ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount()) - : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount()); - await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest'); - - // 2. Alice cannot mint and nest token: - const nestedToken2 = await collectionForNesting.mintToken(alice); - await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); - }); -}); - -itSub('Owner cannot nest FT if nesting is disabled', async ({helper}) => { - // Create default collection, permissions are not set: - const aliceNFTCollection = await helper.nft.mintCollection(alice); - const targetToken = await aliceNFTCollection.mintToken(alice); - - const collectionForNesting = await helper.ft.mintCollection(alice); - - // 1. Alice cannot create immediately nested tokens: - await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); - - // 2. Alice can mint and nest token: - await collectionForNesting.mint(alice, 100n); - await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); -}); - -[ - {mode: 'nft' as const}, - {mode: 'rft' as const}, - {mode: 'ft' as const}, -].map(testCase => { - itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => { - const targetCollection = await helper.nft.mintCollection(alice, {permissions: - {nesting: {tokenOwner: true, collectionAdmin: true}}, - }); - const targetToken = await targetCollection.mintToken(alice); - - const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob); - - let nestedTokenBob: UniqueNFToken | UniqueRFToken; - switch (testCase.mode) { - case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break; - case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break; - case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break; - } - - // Bob non-owner of targetToken and non admin of targetCollection, so - // 1. cannot mint nested token: - switch (testCase.mode) { - case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; - case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; - case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; - } - - // 2. cannot nest existing token: - switch (testCase.mode) { - case 'nft': - case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; - case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; - } - }); -}); - -[ - {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}}, - {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}}, -].map(testCase => { - itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => { - // Create collection with tokenOwner or create collection with collectionAdmin permission: - const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}}); - const targetTokenAlice = await targetCollection.mintToken(alice); - await targetCollection.addAdmin(alice, {Substrate: bob.address}); - - const nestedCollectionAlice = await helper[testCase.mode].mintCollection(alice); - const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob); - // if nesting permissions restricted for token owner – minter is bob (admin), - // if collectionAdmin – alice (owner) - - // FIXME: nesting allowed only for admin but token owner can nest anyway: - testCase.nesting.tokenOwner - ? await expect(nestedCollectionBob.mintToken(bob, targetTokenAlice.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/) - : await expect(nestedCollectionAlice.mintToken(alice, targetTokenAlice.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - }); -}); - -itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => { - const collection = await helper.nft.mintCollection(alice); - // To avoid UserIsNotAllowedToNest error - await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}}); - - // The list of non-existing tokens: - const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1); - const tokenBurnt = await collection.mintToken(alice); - await tokenBurnt.burn(alice); - const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2); - - // The list of collections to nest tokens from: - const nftCollectionForNesting = await helper.nft.mintCollection(alice); - const rftCollectionForNesting = await helper.rft.mintCollection(alice); - const ftCollectionForNesting = await helper.ft.mintCollection(alice); - - const testCases = [ - {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'}, - {token: tokenBurnt, error: 'TokenNotFound'}, - {token: tokenNotMintedYet, error: 'TokenNotFound'}, - ]; - - for(const testCase of testCases) { - // 1. Alice cannot create nested token to non-existing token - await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); - await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); - await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); - - // 2. Alice cannot mint and nest token: - const nft = await nftCollectionForNesting.mintToken(alice); - const rft = await rftCollectionForNesting.mintToken(alice, 100n); - const _ft = await ftCollectionForNesting.mint(alice, 100n); - await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); - await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); - await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error); - } -}); - -itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => { - const existingCollection = await helper.nft.mintCollection(alice); - const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId); - const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999); - - const nftCollectionForNesting = await helper.nft.mintCollection(alice); - - // 1. Alice cannot create nested token to collection address - await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); - await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); - - // 2. Alice cannot mint and nest token to collection address: - const nft = await nftCollectionForNesting.mintToken(alice); - await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); - await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); -}); - -itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => { - // Create default collection, permissions are not set: - const rftCollection = await helper.rft.mintCollection(alice); - const ftCollection = await helper.ft.mintCollection(alice); - - const rftToken = await rftCollection.mintToken(alice); - const _ftToken = await ftCollection.mint(alice, 100n); - - const collectionForNesting = await helper.nft.mintCollection(alice); - - // 1. Alice cannot create immediately nested tokens: - await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting'); - await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting'); - - // 2. Alice cannot mint and nest token: - const nestedToken2 = await collectionForNesting.mintToken(alice); - await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting'); - await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting'); -}); - -itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => { - const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice); - const notAllowedCollectionNFT = await helper.nft.mintCollection(alice); - const notAllowedCollectionRFT = await helper.rft.mintCollection(alice); - const notAllowedCollectionFT = await helper.ft.mintCollection(alice); - - // Collection restricted to allowedCollectionId - const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions: - {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}}, - }); - // Create collection with restricted nesting -- even self is not allowed - const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions: - {nesting: {tokenOwner: true, restricted: []}}, - }); - - const targetTokenA = await restrictedCollectionA.mintToken(alice); - const targetTokenB = await restrictedCollectionB.mintToken(alice); - - // 1. Cannot mint in own collection after allowlisting the accounts: - await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - - // 2. Cannot mint from notAllowedCollection: - await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); -}); - -itSub('Cannot create nesting chains greater than 5', async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); - let token = await collection.mintToken(alice); - - const maxNestingLevel = 5; - - // Create a nested-token matryoshka - for (let i = 0; i < maxNestingLevel; i++) { - token = await collection.mintToken(alice, token.nestingAccount()); - } - - // The nesting depth is limited by `maxNestingLevel` - // 1. Cannot mint: - await expect(collection.mintToken(alice, token.nestingAccount())) - .to.be.rejectedWith(/structure\.DepthLimit/); - // 2. Cannot transfer: - const anotherToken = await collection.mintToken(alice); - await expect(anotherToken.transfer(alice, token.nestingAccount())) - .to.be.rejectedWith(/structure\.DepthLimit/); - // 3. Cannot nest FT pieces: - const ftCollection = await helper.ft.mintCollection(alice); - await expect(ftCollection.mint(alice, 100n, token.nestingAccount())) - .to.be.rejectedWith(/structure\.DepthLimit/); - - expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); - expect(await token.getChildren()).to.has.length(0); -}); --- /dev/null +++ b/tests/src/sub/nesting/nesting.negative.test.ts @@ -0,0 +1,260 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itSub, Pallets, usingPlaygrounds} from '../../util'; +import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../../util/playgrounds/unique'; +import {itEth} from '../../eth/util'; + +let alice: IKeyringPair; +let bob: IKeyringPair; +let charlie: IKeyringPair; + +before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const donor = await privateKey({filename: __filename}); + [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor); + }); +}); + +[ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, +].map(testCase => { + itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => { + // Create default collection, permissions are not set: + const aliceNFTCollection = await helper.nft.mintCollection(alice); + const targetToken = await aliceNFTCollection.mintToken(alice); + + const collectionForNesting = await helper[testCase.mode].mintCollection(alice); + + // 1. Alice cannot create immediately nested tokens: + const nestingTx = testCase.mode === 'nft' + ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount()) + : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount()); + await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + + // 2. Alice cannot mint and nest token: + const nestedToken2 = await collectionForNesting.mintToken(alice); + await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + }); +}); + +itSub('Owner cannot nest FT if nesting is disabled', async ({helper}) => { + // Create default collection, permissions are not set: + const aliceNFTCollection = await helper.nft.mintCollection(alice); + const targetToken = await aliceNFTCollection.mintToken(alice); + + const collectionForNesting = await helper.ft.mintCollection(alice); + + // 1. Alice cannot create immediately nested tokens: + await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + + // 2. Alice can mint and nest token: + await collectionForNesting.mint(alice, 100n); + await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); +}); + +[ + {mode: 'nft' as const}, + {mode: 'rft' as const}, + {mode: 'ft' as const}, +].map(testCase => { + itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => { + const targetCollection = await helper.nft.mintCollection(alice, {permissions: + {nesting: {tokenOwner: true, collectionAdmin: true}}, + }); + const targetToken = await targetCollection.mintToken(alice); + + const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob); + + let nestedTokenBob: UniqueNFToken | UniqueRFToken; + switch (testCase.mode) { + case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break; + case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break; + case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break; + } + + // Bob non-owner of targetToken and non admin of targetCollection, so + // 1. cannot mint nested token: + switch (testCase.mode) { + case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; + case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; + case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; + } + + // 2. cannot nest existing token: + switch (testCase.mode) { + case 'nft': + case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; + case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; + } + }); +}); + +[ + {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}}, + {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}}, +].map(testCase => { + itSub.only(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => { + // Create collection with tokenOwner or create collection with collectionAdmin permission: + const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}}); + const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address}); + await targetCollection.addAdmin(alice, {Substrate: bob.address}); + + const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie); + const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob); + // if nesting permissions restricted for token owner – minter is bob (admin), + // if collectionAdmin – charlie (owner) + testCase.nesting.tokenOwner + ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/) + : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + }); +}); + +itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + // To avoid UserIsNotAllowedToNest error + await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}}); + + // The list of non-existing tokens: + const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1); + const tokenBurnt = await collection.mintToken(alice); + await tokenBurnt.burn(alice); + const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2); + + // The list of collections to nest tokens from: + const nftCollectionForNesting = await helper.nft.mintCollection(alice); + const rftCollectionForNesting = await helper.rft.mintCollection(alice); + const ftCollectionForNesting = await helper.ft.mintCollection(alice); + + const testCases = [ + {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'}, + {token: tokenBurnt, error: 'TokenNotFound'}, + {token: tokenNotMintedYet, error: 'TokenNotFound'}, + ]; + + for(const testCase of testCases) { + // 1. Alice cannot create nested token to non-existing token + await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); + await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); + await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); + + // 2. Alice cannot mint and nest token: + const nft = await nftCollectionForNesting.mintToken(alice); + const rft = await rftCollectionForNesting.mintToken(alice, 100n); + const _ft = await ftCollectionForNesting.mint(alice, 100n); + await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); + await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); + await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error); + } +}); + +itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => { + const existingCollection = await helper.nft.mintCollection(alice); + const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId); + const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999); + + const nftCollectionForNesting = await helper.nft.mintCollection(alice); + + // 1. Alice cannot create nested token to collection address + await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); + await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); + + // 2. Alice cannot mint and nest token to collection address: + const nft = await nftCollectionForNesting.mintToken(alice); + await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); + await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); +}); + +itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => { + // Create default collection, permissions are not set: + const rftCollection = await helper.rft.mintCollection(alice); + const ftCollection = await helper.ft.mintCollection(alice); + + const rftToken = await rftCollection.mintToken(alice); + const _ftToken = await ftCollection.mint(alice, 100n); + + const collectionForNesting = await helper.nft.mintCollection(alice); + + // 1. Alice cannot create immediately nested tokens: + await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting'); + await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting'); + + // 2. Alice cannot mint and nest token: + const nestedToken2 = await collectionForNesting.mintToken(alice); + await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting'); + await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting'); +}); + +itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => { + const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice); + const notAllowedCollectionNFT = await helper.nft.mintCollection(alice); + const notAllowedCollectionRFT = await helper.rft.mintCollection(alice); + const notAllowedCollectionFT = await helper.ft.mintCollection(alice); + + // Collection restricted to allowedCollectionId + const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions: + {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}}, + }); + // Create collection with restricted nesting -- even self is not allowed + const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions: + {nesting: {tokenOwner: true, restricted: []}}, + }); + + const targetTokenA = await restrictedCollectionA.mintToken(alice); + const targetTokenB = await restrictedCollectionB.mintToken(alice); + + // 1. Cannot mint in own collection after allowlisting the accounts: + await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + + // 2. Cannot mint from notAllowedCollection: + await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); +}); + +itSub('Cannot create nesting chains greater than 5', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + let token = await collection.mintToken(alice); + + const maxNestingLevel = 5; + + // Create a nested-token matryoshka + for (let i = 0; i < maxNestingLevel; i++) { + token = await collection.mintToken(alice, token.nestingAccount()); + } + + // The nesting depth is limited by `maxNestingLevel` + // 1. Cannot mint: + await expect(collection.mintToken(alice, token.nestingAccount())) + .to.be.rejectedWith(/structure\.DepthLimit/); + // 2. Cannot transfer: + const anotherToken = await collection.mintToken(alice); + await expect(anotherToken.transfer(alice, token.nestingAccount())) + .to.be.rejectedWith(/structure\.DepthLimit/); + // 3. Cannot nest FT pieces: + const ftCollection = await helper.ft.mintCollection(alice); + await expect(ftCollection.mint(alice, 100n, token.nestingAccount())) + .to.be.rejectedWith(/structure\.DepthLimit/); + + expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); + expect(await token.getChildren()).to.has.length(0); +}); --- /dev/null +++ b/tests/src/sub/nesting/refungible.test.ts @@ -0,0 +1,60 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itSub, Pallets, usingPlaygrounds} from '../../util'; + +// ReFungible specific nesting tests +let alice: IKeyringPair; + +before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const donor = await privateKey({filename: __filename}); + [alice] = await helper.arrange.createAccounts([200n], donor); + }); +}); + +itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice, { + permissions: { + nesting: { + tokenOwner: true, + }, + }, + }); + const collectionRFT = await helper.rft.mintCollection(alice); + + const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address}); + const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address}); + + expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address}); + + await rft.transfer(alice, nft.nestingAccount(), 40n); + + expect(await rft.getTopmostOwner()).deep.equal(null); + + await rft.transfer(alice, nft.nestingAccount(), 60n); + + expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address}); + + await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n); + + expect(await rft.getTopmostOwner()).deep.equal(null); + + await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n); + + expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address}); +}); \ No newline at end of file --- /dev/null +++ b/tests/src/sub/nesting/unnesting.negative.test.ts @@ -0,0 +1,76 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itSub, usingPlaygrounds} from '../../util'; + +describe('Negative Test: Unnesting', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const donor = await privateKey({filename: __filename}); + [alice, bob] = await helper.arrange.createAccounts([100n, 50n], donor); + }); + }); + + // TODO: make this test a bit more generic + itSub('Admin (NFT): disallows an Admin to unnest someone else\'s token', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {limits: {ownerCanTransfer: true}, permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}}); + //await collection.addAdmin(alice, {Substrate: bob.address}); + const targetToken = await collection.mintToken(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, targetToken.nestingAccount()); + + // Try to nest somebody else's token + const newToken = await collection.mintToken(bob); + await expect(newToken.nest(alice, targetToken)) + .to.be.rejectedWith(/common\.NoPermission/); + + // Try to unnest a token belonging to someone else as collection admin + const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); + await expect(nestedToken.unnest(alice, targetToken, {Substrate: bob.address})) + .to.be.rejectedWith(/common\.AddressNotInAllowlist/); + + expect(await targetToken.getChildren()).to.be.length(1); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + }); + + [ + {restrictedMode: true}, + {restrictedMode: false}, + ].map(testCase => { + itSub(`Fungible: disallows a non-Owner to unnest someone else's token ${testCase.restrictedMode ? '(Restricted nesting)' : ''}`, async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address}); + + await collectionNFT.setPermissions(alice, {nesting: { + collectionAdmin: true, tokenOwner: true, restricted: testCase.restrictedMode ? [collectionFT.collectionId] : null, + }}); + + // Nest some tokens as Alice into Bob's token + await collectionFT.mint(alice, 5n, targetToken.nestingAccount()); + + // Try to pull it out as Alice still + await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n)) + .to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); + }); + }); +}); -- gitstuff