difftreelog
tests(nesting): refactored to use playgrounds
in: master
10 files changed
tests/src/eth/util/playgrounds/index.tsdiffbeforeafterbothno syntactic changes
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth240 }240 }241241242 fromTokenId(collectionId: number, tokenId: number): string {242 fromTokenId(collectionId: number, tokenId: number): string {243 return this.helper.util.getNestingTokenAddress(collectionId, tokenId);243 return this.helper.util.getNestingTokenAddressRaw({collectionId, tokenId});244 }244 }245245246 normalizeAddress(address: string): string {246 normalizeAddress(address: string): string {tests/src/nesting/graphs.test.tsdiffbeforeafterboth1import {ApiPromise} from '@polkadot/api';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.162import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';3import {expect} from 'chai';18import {expect, itSub, usingPlaygrounds} from '../util/playgrounds';4import {tokenIdToCross} from '../eth/util/helpers';5import usingApi, {executeTransaction} from '../substrate/substrate-api';6import {getCreateCollectionResult, transferExpectSuccess, setCollectionLimitsExpectSuccess} from '../util/helpers';19import {UniqueHelper, UniqueNFTToken} from '../util/playgrounds/unique';7208/**21/**9 * ```dot22 * ```dot12 * 8 -> 525 * 8 -> 513 * ```26 * ```14 */27 */15async function buildComplexObjectGraph(api: ApiPromise, sender: IKeyringPair): Promise<number> {28async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise<UniqueNFTToken[]> {16 const events = await executeTransaction(api, sender, api.tx.unique.createCollectionEx({mode: 'NFT', permissions: {nesting: {tokenOwner: true}}}));29 const collection = await helper.nft.mintCollection(sender, {permissions: {nesting: {tokenOwner: true}}});17 const {collectionId} = getCreateCollectionResult(events);30 const tokens = await collection.mintMultipleTokens(sender, Array(8).fill({owner: {Substrate: sender.address}}));1819 await executeTransaction(api, sender, api.tx.unique.createMultipleItemsEx(collectionId, {NFT: Array(8).fill({owner: {Substrate: sender.address}})}));203121 await transferExpectSuccess(collectionId, 8, sender, tokenIdToCross(collectionId, 5));32 await tokens[7].nest(sender, tokens[4]);2223 await transferExpectSuccess(collectionId, 7, sender, tokenIdToCross(collectionId, 6));33 await tokens[6].nest(sender, tokens[5]);24 await transferExpectSuccess(collectionId, 6, sender, tokenIdToCross(collectionId, 5));34 await tokens[5].nest(sender, tokens[4]);25 await transferExpectSuccess(collectionId, 5, sender, tokenIdToCross(collectionId, 2));35 await tokens[4].nest(sender, tokens[1]);2627 await transferExpectSuccess(collectionId, 4, sender, tokenIdToCross(collectionId, 3));36 await tokens[3].nest(sender, tokens[2]);28 await transferExpectSuccess(collectionId, 3, sender, tokenIdToCross(collectionId, 2));37 await tokens[2].nest(sender, tokens[1]);29 await transferExpectSuccess(collectionId, 2, sender, tokenIdToCross(collectionId, 1));38 await tokens[1].nest(sender, tokens[0]);303931 return collectionId;40 return tokens;32}41}334234describe('Graphs', () => {43describe('Graphs', () => {44 let alice: IKeyringPair;4546 before(async () => {47 await usingPlaygrounds(async (helper, privateKey) => {48 const donor = privateKey('//Alice');49 [alice] = await helper.arrange.createAccounts([10n], donor);50 });51 });5235 it('Ouroboros can\'t be created in a complex graph', async () => {53 itSub('Ouroboros can\'t be created in a complex graph', async ({helper}) => {36 await usingApi(async (api, privateKeyWrapper) => {54 const tokens = await buildComplexObjectGraph(helper, alice);37 const alice = privateKeyWrapper('//Alice');38 const collection = await buildComplexObjectGraph(api, alice);39 const tokenTwoParent = tokenIdToCross(collection, 1);405541 // to self56 // to self42 await expect(57 await expect(43 executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 1), collection, 1, 1)),58 tokens[0].nest(alice, tokens[0]),44 'first transaction', 59 'first transaction', 45 ).to.be.rejectedWith(/structure\.OuroborosDetected/);60 ).to.be.rejectedWith(/structure\.OuroborosDetected/);46 // to nested part of graph61 // to nested part of graph47 await expect(62 await expect(48 executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 5), collection, 1, 1)),63 tokens[0].nest(alice, tokens[4]),49 'second transaction',64 'second transaction',50 ).to.be.rejectedWith(/structure\.OuroborosDetected/);65 ).to.be.rejectedWith(/structure\.OuroborosDetected/);51 await expect(66 await expect(52 executeTransaction(api, alice, api.tx.unique.transferFrom(tokenTwoParent, tokenIdToCross(collection, 8), collection, 2, 1)),67 tokens[1].transferFrom(alice, tokens[0].nestingAddress(), tokens[7].nestingAddress()),53 'third transaction',68 'third transaction',54 ).to.be.rejectedWith(/structure\.OuroborosDetected/);69 ).to.be.rejectedWith(/structure\.OuroborosDetected/);55 });56 });70 });57});71});5872tests/src/nesting/migration-check.test.tsdiffbeforeafterboth8import find from 'find-process';8import find from 'find-process';9910// todo un-skip for migrations10// todo un-skip for migrations11// todo:playgrounds skipped, this one is outdated. Probably to be deleted/replaced.11describe.skip('Migration testing', () => {12describe.skip('Migration testing: Properties', () => {12 let alice: IKeyringPair;13 let alice: IKeyringPair;131414 before(async() => {15 before(async() => {tests/src/nesting/nest.test.tsdiffbeforeafterboth1import {expect} from 'chai';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2import {tokenIdToAddress} from '../eth/util/helpers';3import usingApi, {executeTransaction} from '../substrate/substrate-api';2// This file is part of Unique Network.4import {5 addCollectionAdminExpectSuccess,6 addToAllowListExpectSuccess,7 createCollectionExpectSuccess,8 createItemExpectSuccess,9 enableAllowListExpectSuccess,10 enablePublicMintingExpectSuccess,11 getTokenChildren,12 getTokenOwner,13 getTopmostTokenOwner,14 normalizeAccountId,15 setCollectionPermissionsExpectSuccess,16 transferExpectFailure,17 transferExpectSuccess,18 transferFromExpectSuccess,19 setCollectionLimitsExpectSuccess,20 requirePallets,21 Pallets,22} from '../util/helpers';23import {IKeyringPair} from '@polkadot/types/types';24325let alice: IKeyringPair;4// Unique Network is free software: you can redistribute it and/or modify26let bob: IKeyringPair;5// it under the terms of the GNU General Public License as published by27let charlie: IKeyringPair;6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.2889// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {expect, itSub, Pallets, usingPlaygrounds} from '../util/playgrounds';1929describe('Integration Test: Composite nesting tests', () => {20describe('Integration Test: Composite nesting tests', () => {21 let alice: IKeyringPair;22 let bob: IKeyringPair;2330 before(async () => {24 before(async () => {31 await usingApi(async (_, privateKeyWrapper) => {25 await usingPlaygrounds(async (helper, privateKey) => {32 alice = privateKeyWrapper('//Alice');26 const donor = privateKey('//Alice');33 bob = privateKeyWrapper('//Bob');27 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);34 });28 });35 });29 });363037 it('Performs the full suite: bundles a token, transfers, and unnests', async () => {31 itSub('Performs the full suite: bundles a token, transfers, and unnests', async ({helper}) => {38 await usingApi(async api => {32 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});39 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});40 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});41 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');33 const targetToken = await collection.mintToken(alice);423443 // Create a nested token35 // Create an immediately nested token44 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});36 const nestedToken = await collection.mintToken(alice, targetToken.nestingAddress());45 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});37 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});46 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});38 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());39 40 // Create a token to be nested41 const newToken = await collection.mintToken(alice);474248 // Create a token to be nested43 // Nest44 await newToken.nest(alice, targetToken);49 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');45 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});46 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());504751 // Nest48 // Move bundle to different user52 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});49 await targetToken.transfer(alice, {Substrate: bob.address});53 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});50 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});54 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});51 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());52 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});53 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());555456 // Move bundle to different user55 // Unnest57 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});56 await newToken.unnest(bob, targetToken, {Substrate: bob.address});58 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});57 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});59 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});58 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address});6061 // Unnest62 await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});63 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});64 });65 });59 });666067 it('Transfers an already bundled token', async () => {61 itSub('Transfers an already bundled token', async ({helper}) => {68 await usingApi(async api => {62 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});69 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});70 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});63 const tokenA = await collection.mintToken(alice);64 const tokenB = await collection.mintToken(alice);716572 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');66 // Create a nested token73 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT');7475 // Create a nested token76 const tokenC = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});67 const tokenC = await collection.mintToken(alice, tokenA.nestingAddress());77 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});68 expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAddress());78 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenA).toLowerCase()});7980 // Transfer the nested token to another token69 70 // Transfer the nested token to another token81 await expect(executeTransaction(71 await expect(tokenC.transferFrom(alice, tokenA.nestingAddress(), tokenB.nestingAddress())).to.be.fulfilled;82 api,83 alice,84 api.tx.unique.transferFrom(85 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenA)}),86 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenB)}),87 collection,88 tokenC,89 1,90 ),91 )).to.not.be.rejected;92 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});72 expect(await tokenC.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});93 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenB).toLowerCase()});73 expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAddress());94 });95 });74 });967597 it('Checks token children', async () => {76 itSub('Checks token children', async ({helper}) => {98 await usingApi(async api => {77 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});99 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});100 await setCollectionLimitsExpectSuccess(alice, collectionA, {ownerCanTransfer: true});78 const collectionB = await helper.ft.mintCollection(alice);101 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {tokenOwner: true}});79 80 const targetToken = await collectionA.mintToken(alice);102 const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});81 expect((await targetToken.getChildren()).length).to.be.equal(0, 'Children length check at creation');10382104 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');83 // Create a nested NFT token105 const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};84 const tokenA = await collectionA.mintToken(alice, targetToken.nestingAddress());106 let children = await getTokenChildren(api, collectionA, targetToken);85 expect(await targetToken.getChildren()).to.have.deep.members([86 {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},107 expect(children.length).to.be.equal(0, 'Children length check at creation');87 ], 'Children contents check at nesting #1').and.be.length(1, 'Children length check at nesting #1');10888109 // Create a nested NFT token89 // Create then nest110 const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);90 const tokenB = await collectionA.mintToken(alice);111 children = await getTokenChildren(api, collectionA, targetToken);91 await tokenB.nest(alice, targetToken);112 expect(children.length).to.be.equal(1, 'Children length check at nesting #1');92 expect(await targetToken.getChildren()).to.have.deep.members([113 expect(children).to.have.deep.members([114 {token: tokenA, collection: collectionA},93 {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},115 ], 'Children contents check at nesting #1');94 {tokenId: tokenB.tokenId, collectionId: collectionA.collectionId},95 ], 'Children contents check at nesting #2').and.be.length(2, 'Children length check at nesting #2');11696117 // Create then nest97 // Move token B to a different user outside the nesting tree118 const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');119 await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);98 await tokenB.unnest(alice, targetToken, {Substrate: bob.address});120 children = await getTokenChildren(api, collectionA, targetToken);121 expect(children.length).to.be.equal(2, 'Children length check at nesting #2');99 expect(await targetToken.getChildren()).to.be.have.deep.members([122 expect(children).to.have.deep.members([123 {token: tokenA, collection: collectionA},100 {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},124 {token: tokenB, collection: collectionA},101 ], 'Children contents check at nesting #3 (unnesting)').and.be.length(1, 'Children length check at nesting #3 (unnesting)');125 ], 'Children contents check at nesting #2');126102127 // Move token B to a different user outside the nesting tree103 // Create a fungible token in another collection and then nest104 await collectionB.mint(alice, 10n);128 await transferFromExpectSuccess(collectionA, tokenB, alice, targetAddress, bob);105 await collectionB.transfer(alice, targetToken.nestingAddress(), 2n);106 expect(await targetToken.getChildren()).to.be.have.deep.members([107 {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},108 {tokenId: 0, collectionId: collectionB.collectionId},109 ], 'Children contents check at nesting #4 (from another collection)')110 .and.be.length(2, 'Children length check at nesting #4 (from another collection)');129 children = await getTokenChildren(api, collectionA, targetToken);111 112 // Move part of the fungible token inside token A deeper in the nesting tree113 await collectionB.transferFrom(alice, targetToken.nestingAddress(), tokenA.nestingAddress(), 1n);130 expect(children.length).to.be.equal(1, 'Children length check at unnesting');114 expect(await targetToken.getChildren()).to.be.have.deep.members([115 {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},116 {tokenId: 0, collectionId: collectionB.collectionId},117 ], 'Children contents check at nesting #5 (deeper)').and.be.length(2, 'Children length check at nesting #5 (deeper)');131 expect(children).to.be.have.deep.members([118 expect(await tokenA.getChildren()).to.be.have.deep.members([132 {token: tokenA, collection: collectionA},119 {tokenId: 0, collectionId: collectionB.collectionId},133 ], 'Children contents check at unnesting');120 ], 'Children contents check at nesting #5.5 (deeper)').and.be.length(1, 'Children length check at nesting #5.5 (deeper)');134121135 // Create a fungible token in another collection and then nest122 // Move the remaining part of the fungible token inside token A deeper in the nesting tree136 const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');137 await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');123 await collectionB.transferFrom(alice, targetToken.nestingAddress(), tokenA.nestingAddress(), 1n);138 children = await getTokenChildren(api, collectionA, targetToken);139 expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');124 expect(await targetToken.getChildren()).to.be.have.deep.members([140 expect(children).to.be.have.deep.members([141 {token: tokenA, collection: collectionA},125 {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},142 {token: tokenC, collection: collectionB},126 ], 'Children contents check at nesting #6 (deeper)').and.be.length(1, 'Children length check at nesting #6 (deeper)');143 ], 'Children contents check at nesting #3 (from another collection)');144145 // Move the fungible token inside token A deeper in the nesting tree146 await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');147 children = await getTokenChildren(api, collectionA, targetToken);148 expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');149 expect(children).to.be.have.deep.members([127 expect(await tokenA.getChildren()).to.be.have.deep.members([150 {token: tokenA, collection: collectionA},128 {tokenId: 0, collectionId: collectionB.collectionId},151 ], 'Children contents check at deeper nesting');129 ], 'Children contents check at nesting #6.5 (deeper)').and.be.length(1, 'Children length check at nesting #6.5 (deeper)');152 });153 });130 });154});131});155132156describe('Integration Test: Various token type nesting', async () => {133describe('Integration Test: Various token type nesting', () => {134 let alice: IKeyringPair;135 let bob: IKeyringPair;136 let charlie: IKeyringPair;137157 before(async () => {138 before(async () => {158 await usingApi(async (_, privateKeyWrapper) => {139 await usingPlaygrounds(async (helper, privateKey) => {159 alice = privateKeyWrapper('//Alice');140 const donor = privateKey('//Alice');160 bob = privateKeyWrapper('//Bob');141 [alice, bob, charlie] = await helper.arrange.createAccounts([50n, 10n, 10n], donor);161 charlie = privateKeyWrapper('//Charlie');162 });142 });163 });143 });164144165 it('Admin (NFT): allows an Admin to nest a token', async () => {145 itSub('Admin (NFT): allows an Admin to nest a token', async ({helper}) => {166 await usingApi(async api => {146 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true}}});167 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});168 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});169 await addCollectionAdminExpectSuccess(alice, collection, bob.address);147 await collection.addAdmin(alice, {Substrate: bob.address});170 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);148 const targetToken = await collection.mintToken(alice, {Substrate: charlie.address});171149172 // Create a nested token150 // Create an immediately nested token173 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});151 const nestedToken = await collection.mintToken(bob, targetToken.nestingAddress());174 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});152 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});175 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});153 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());176154177 // Create a token to be nested and nest155 // Create a token to be nested and nest178 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');156 const newToken = await collection.mintToken(bob);179 await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)});157 await newToken.nest(bob, targetToken);180 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});158 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});181 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});159 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());182 });183 });160 });184161185 it('Admin (NFT): Admin and Token Owner can operate together', async () => {162 itSub('Admin (NFT): Admin and Token Owner can operate together', async ({helper}) => {186 await usingApi(async api => {163 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true}}});187 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});188 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, collectionAdmin: true}});189 await addCollectionAdminExpectSuccess(alice, collection, bob.address);164 await collection.addAdmin(alice, {Substrate: bob.address});190 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);165 const targetToken = await collection.mintToken(alice, {Substrate: charlie.address});191166192 // Create a nested token by an administrator167 // Create an immediately nested token by an administrator193 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});168 const nestedToken = await collection.mintToken(bob, targetToken.nestingAddress());194 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});169 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});195 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});170 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());196171197 // Create a token and allow the owner to nest too172 // Create a token to be nested and nest198 const newToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);173 const newToken = await collection.mintToken(alice, {Substrate: charlie.address});199 await transferExpectSuccess(collection, newToken, charlie, {Ethereum: tokenIdToAddress(collection, nestedToken)});174 await newToken.nest(charlie, targetToken);200 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});175 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});201 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, nestedToken).toLowerCase()});176 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());202 });203 });177 });204178205 it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => {179 itSub('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async ({helper}) => {206 await usingApi(async api => {180 const collectionA = await helper.nft.mintCollection(alice);207 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208 await addCollectionAdminExpectSuccess(alice, collectionA, bob.address);181 await collectionA.addAdmin(alice, {Substrate: bob.address});209 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});182 const collectionB = await helper.nft.mintCollection(alice);210 await addCollectionAdminExpectSuccess(alice, collectionB, bob.address);183 await collectionB.addAdmin(alice, {Substrate: bob.address});211 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA, collectionB]}});184 await collectionA.setPermissions(alice, {nesting: {collectionAdmin: true, restricted:[collectionB.collectionId]}});212 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT', charlie.address);185 const targetToken = await collectionA.mintToken(alice, {Substrate: charlie.address});213186214 // Create a nested token187 // Create an immediately nested token215 const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)});188 const nestedToken = await collectionB.mintToken(bob, targetToken.nestingAddress());216 expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: charlie.address});189 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});217 expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});190 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());218191219 // Create a token to be nested and nest192 // Create a token to be nested and nest220 const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT');193 const newToken = await collectionB.mintToken(bob);221 await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)});194 await newToken.nest(bob, targetToken);222 expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: charlie.address});195 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});223 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});196 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());224 });225 });197 });226198227 // ---------- Non-Fungible ----------199 // ---------- Non-Fungible ----------228200229 it('NFT: allows an Owner to nest/unnest their token', async () => {201 itSub('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {230 await usingApi(async api => {202 const collection = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}});231 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});232 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});203 await collection.addToAllowList(alice, {Substrate: charlie.address});233 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');204 const targetToken = await collection.mintToken(charlie);205 await collection.addToAllowList(alice, targetToken.nestingAddress());234206235 // Create a nested token207 // Create an immediately nested token236 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});208 const nestedToken = await collection.mintToken(charlie, targetToken.nestingAddress());237 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});209 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});238 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});210 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());239211240 // Create a token to be nested and nest212 // Create a token to be nested and nest241 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');213 const newToken = await collection.mintToken(charlie);242 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});214 await newToken.nest(charlie, targetToken);243 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});215 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});244 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});216 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());245 });246 });217 });247218248 it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {219 itSub('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {249 await usingApi(async api => {220 const collectionA = await helper.nft.mintCollection(alice);250 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});221 const collectionB = await helper.nft.mintCollection(alice);251 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});222 //await collectionB.addAdmin(alice, {Substrate: bob.address});252 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');223 const targetToken = await collectionA.mintToken(alice, {Substrate: charlie.address});253224254 // Create a nested token225 await collectionA.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionB.collectionId]}});255 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});256 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});226 await collectionA.addToAllowList(alice, {Substrate: charlie.address});257 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});227 await collectionA.addToAllowList(alice, targetToken.nestingAddress());258228259 // Create a token to be nested and nest229 await collectionB.setPermissions(alice, {access: 'AllowList', mintMode: true});260 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');230 await collectionB.addToAllowList(alice, {Substrate: charlie.address});231 await collectionB.addToAllowList(alice, targetToken.nestingAddress());232261 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});233 // Create an immediately nested token234 const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAddress());262 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});235 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});263 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});236 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());237238 // Create a token to be nested and nest239 const newToken = await collectionB.mintToken(charlie);240 await newToken.nest(charlie, targetToken);241 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});264 });242 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());265 });243 });266244267 // ---------- Fungible ----------245 // ---------- Fungible ----------268246269 it('Fungible: allows an Owner to nest/unnest their token', async () => {247 itSub('Fungible: allows an Owner to nest/unnest their token', async ({helper}) => {270 await usingApi(async api => {248 const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}});271 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});272 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});273 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});249 const collectionFT = await helper.ft.mintCollection(alice);274 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};250 const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});275251276 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});252 await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});253 await collectionNFT.addToAllowList(alice, targetToken.nestingAddress());277254278 // Create a nested token255 await collectionFT.setPermissions(alice, {access: 'AllowList', mintMode: true});279 await expect(executeTransaction(api, alice, api.tx.unique.createItem(280 collectionFT,256 await collectionFT.addToAllowList(alice, {Substrate: charlie.address});281 targetAddress,282 {Fungible: {Value: 10}},283 ))).to.not.be.rejected;257 await collectionFT.addToAllowList(alice, targetToken.nestingAddress());284258285 // Nest a new token259 // Create an immediately nested token286 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');260 await collectionFT.mint(charlie, 5n, targetToken.nestingAddress());261 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(5n);262287 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');263 // Create a token to be nested and nest264 await collectionFT.mint(charlie, 5n);265 await collectionFT.transfer(charlie, targetToken.nestingAddress(), 2n);288 });266 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(7n);289 });267 });290268291 it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {269 itSub('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {292 await usingApi(async api => {270 const collectionNFT = await helper.nft.mintCollection(alice);293 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});271 const collectionFT = await helper.ft.mintCollection(alice);294 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});272 const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});295 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};296273297 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});274 await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionFT.collectionId]}});275 await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});276 await collectionNFT.addToAllowList(alice, targetToken.nestingAddress());298277299 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted: [collectionFT]}});278 await collectionFT.setPermissions(alice, {access: 'AllowList', mintMode: true});279 await collectionFT.addToAllowList(alice, {Substrate: charlie.address});280 await collectionFT.addToAllowList(alice, targetToken.nestingAddress());300281301 // Create a nested token282 // Create an immediately nested token302 await expect(executeTransaction(api, alice, api.tx.unique.createItem(283 await collectionFT.mint(charlie, 5n, targetToken.nestingAddress());303 collectionFT,284 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(5n);304 targetAddress,305 {Fungible: {Value: 10}},306 ))).to.not.be.rejected;307285308 // Nest a new token286 // Create a token to be nested and nest309 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');287 await collectionFT.mint(charlie, 5n);310 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');288 await collectionFT.transfer(charlie, targetToken.nestingAddress(), 2n);311 });289 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(7n);312 });290 });313291314 // ---------- Re-Fungible ----------292 // ---------- Re-Fungible ----------315293316 it('ReFungible: allows an Owner to nest/unnest their token', async function() {294 itSub.ifWithPallets('ReFungible: allows an Owner to nest/unnest their token', [Pallets.ReFungible], async ({helper}) => {317 await requirePallets(this, [Pallets.ReFungible]);295 const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}});296 const collectionRFT = await helper.rft.mintCollection(alice);297 const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});318298319 await usingApi(async api => {299 await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});320 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});321 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});322 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});323 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};300 await collectionNFT.addToAllowList(alice, targetToken.nestingAddress());324301325 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});302 await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true});303 await collectionRFT.addToAllowList(alice, {Substrate: charlie.address});304 await collectionRFT.addToAllowList(alice, targetToken.nestingAddress());326305327 // Create a nested token306 // Create an immediately nested token328 await expect(executeTransaction(api, alice, api.tx.unique.createItem(307 const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAddress());329 collectionRFT,308 expect(await nestedToken.getBalance(targetToken.nestingAddress())).to.be.equal(5n);330 targetAddress,331 {ReFungible: {pieces: 100}},332 ))).to.not.be.rejected;333309334 // Nest a new token310 // Create a token to be nested and nest335 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');311 const newToken = await collectionRFT.mintToken(charlie, 5n);336 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');312 await newToken.transfer(charlie, targetToken.nestingAddress(), 2n);337 });313 expect(await newToken.getBalance(targetToken.nestingAddress())).to.be.equal(2n);338 });314 });339315340 it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async function() {316 itSub.ifWithPallets('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', [Pallets.ReFungible], async ({helper}) => {341 await requirePallets(this, [Pallets.ReFungible]);317 const collectionNFT = await helper.nft.mintCollection(alice);318 const collectionRFT = await helper.rft.mintCollection(alice);319 const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});342320343 await usingApi(async api => {321 await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionRFT.collectionId]}});344 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});345 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});322 await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});346 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};323 await collectionNFT.addToAllowList(alice, targetToken.nestingAddress());347324348 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});325 await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true});326 await collectionRFT.addToAllowList(alice, {Substrate: charlie.address});327 await collectionRFT.addToAllowList(alice, targetToken.nestingAddress());349328350 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});329 // Create an immediately nested token330 const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAddress());331 expect(await nestedToken.getBalance(targetToken.nestingAddress())).to.be.equal(5n);351332352 // Create a nested token333 // Create a token to be nested and nest353 await expect(executeTransaction(api, alice, api.tx.unique.createItem(354 collectionRFT,355 targetAddress,356 {ReFungible: {pieces: 100}},357 ))).to.not.be.rejected;358359 // Nest a new token360 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');334 const newToken = await collectionRFT.mintToken(charlie, 5n);361 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');335 await newToken.transfer(charlie, targetToken.nestingAddress(), 2n);362 });336 expect(await newToken.getBalance(targetToken.nestingAddress())).to.be.equal(2n);363 });337 });364});338});365339366describe('Negative Test: Nesting', async() => {340describe('Negative Test: Nesting', () => {341 let alice: IKeyringPair;342 let bob: IKeyringPair;343367 before(async () => {344 before(async () => {368 await usingApi(async (_, privateKeyWrapper) => {345 await usingPlaygrounds(async (helper, privateKey) => {369 alice = privateKeyWrapper('//Alice');346 const donor = privateKey('//Alice');370 bob = privateKeyWrapper('//Bob');347 [alice, bob] = await helper.arrange.createAccounts([100n, 50n], donor);371 });348 });372 });349 });373350374 it('Disallows excessive token nesting', async () => {351 itSub('Disallows excessive token nesting', async ({helper}) => {375 await usingApi(async api => {352 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});376 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});377 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});378 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');353 let token = await collection.mintToken(alice);379354380 const maxNestingLevel = 5;355 const maxNestingLevel = 5;381 let prevToken = targetToken;382356383 // Create a nested-token matryoshka357 // Create a nested-token matryoshka384 for (let i = 0; i < maxNestingLevel; i++) {358 for (let i = 0; i < maxNestingLevel; i++) {385 const nestedToken = await createItemExpectSuccess(359 token = await collection.mintToken(alice, token.nestingAddress());386 alice,387 collection,388 'NFT',389 {Ethereum: tokenIdToAddress(collection, prevToken)},390 );360 }391361392 prevToken = nestedToken;362 // The nesting depth is limited by `maxNestingLevel`393 }394395 // The nesting depth is limited by `maxNestingLevel`396 await expect(executeTransaction(api, alice, api.tx.unique.createItem(363 await expect(collection.mintToken(alice, token.nestingAddress()))397 collection,398 {Ethereum: tokenIdToAddress(collection, prevToken)},399 {nft: {}} as any,364 .to.be.rejectedWith(/structure\.DepthLimit/);400 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);401402 expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address});365 expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});403 });366 expect(await token.getChildren()).to.be.length(0);404 });367 });405368406 // ---------- Admin ------------369 // ---------- Admin ------------407370408 it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => {371 itSub('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async ({helper}) => {409 await usingApi(async api => {372 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});410 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});411 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});412 await addCollectionAdminExpectSuccess(alice, collection, bob.address);373 await collection.addAdmin(alice, {Substrate: bob.address});413 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');374 const targetToken = await collection.mintToken(alice);414375415 // Try to create a nested token as collection admin when it's disallowed376 // Try to create an immediately nested token as collection admin when it's disallowed416 await expect(executeTransaction(api, bob, api.tx.unique.createItem(377 await expect(collection.mintToken(bob, targetToken.nestingAddress()))417 collection,418 {Ethereum: tokenIdToAddress(collection, targetToken)},419 {nft: {}} as any,378 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);420 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);421379422 // Try to create and nest a token in the wrong collection380 // Try to create a token to be nested and nest423 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');381 const newToken = await collection.mintToken(bob);424 await expect(executeTransaction(382 await expect(newToken.nest(bob, targetToken))425 api, 426 bob, 427 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),428 ), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);383 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);384429 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});385 expect(await targetToken.getChildren()).to.be.length(0);430 });386 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address});431 });387 });432388433 it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => {389 itSub('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async ({helper}) => {434 await usingApi(async api => {390 const collection = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}});435 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});436 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});437 await addToAllowListExpectSuccess(alice, collection, bob.address);391 const targetToken = await collection.mintToken(alice, {Substrate: bob.address});438 await enableAllowListExpectSuccess(alice, collection);392 await collection.addToAllowList(alice, {Substrate: bob.address});439 await enablePublicMintingExpectSuccess(alice, collection);393 await collection.addToAllowList(alice, targetToken.nestingAddress());440 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');441394442 // Try to create a nested token as collection admin when it's disallowed395 // Try to create a nested token as token owner when it's disallowed443 await expect(executeTransaction(api, bob, api.tx.unique.createItem(396 await expect(collection.mintToken(bob, targetToken.nestingAddress()))444 collection,445 {Ethereum: tokenIdToAddress(collection, targetToken)},446 {nft: {}} as any,397 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);447 )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); 448398449 // Try to create and nest a token in the wrong collection399 // Try to create a token to be nested and nest450 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');400 const newToken = await collection.mintToken(bob);451 await expect(executeTransaction(401 await expect(newToken.nest(bob, targetToken))452 api, 453 bob, 454 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),402 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);403455 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);404 expect(await targetToken.getChildren()).to.be.length(0);456 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});405 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address});457 });458 });406 });459407460 it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => {408 itSub('Admin (NFT): disallows an Admin to unnest someone else\'s token', async ({helper}) => {461 await usingApi(async api => {409 const collection = await helper.nft.mintCollection(alice, {limits: {ownerCanTransfer: true}, permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}});462 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});410 //await collection.addAdmin(alice, {Substrate: bob.address});463 await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});411 const targetToken = await collection.mintToken(alice, {Substrate: bob.address});464 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});412 await collection.addToAllowList(alice, {Substrate: bob.address});413 await collection.addToAllowList(alice, targetToken.nestingAddress());465414466 await addToAllowListExpectSuccess(alice, collection, bob.address);415 // Try to nest somebody else's token416 const newToken = await collection.mintToken(bob);467 await enableAllowListExpectSuccess(alice, collection);417 await expect(newToken.nest(alice, targetToken))468 await enablePublicMintingExpectSuccess(alice, collection);418 .to.be.rejectedWith(/common\.NoPermission/);469419470 // Create a token to attempt to be nested into420 // Try to unnest a token belonging to someone else as collection admin471 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');421 const nestedToken = await collection.mintToken(alice, targetToken.nestingAddress());472 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()};422 await expect(nestedToken.unnest(alice, targetToken, {Substrate: bob.address}))423 .to.be.rejectedWith(/common\.AddressNotInAllowlist/);473424474 // Try to nest somebody else's token425 expect(await targetToken.getChildren()).to.be.length(1);475 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');476 await expect(executeTransaction(477 api, 478 alice, 479 api.tx.unique.transferFrom(targetAddress, {Substrate: bob.address}, collection, newToken, 1),480 ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);481 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});426 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});482483 // Nest a token as admin and try to unnest it, now belonging to someone else427 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());484 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);485 await expect(executeTransaction(486 api, 487 alice, 488 api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1),489 ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);490 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress);491 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});492 });493 });428 });494429495 it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => {430 itSub('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async ({helper}) => {496 await usingApi(async api => {431 const collectionA = await helper.nft.mintCollection(alice);497 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});498 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});432 const collectionB = await helper.nft.mintCollection(alice);499 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA]}});433 await collectionA.setPermissions(alice, {nesting: {collectionAdmin: true, restricted: [collectionA.collectionId]}});434 const targetToken = await collectionA.mintToken(alice);500435501 // Create a token to attempt to be nested into436 // Try to create a nested token from another collection502 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');437 await expect(collectionB.mintToken(alice, targetToken.nestingAddress()))438 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);503439504 // Try to create and nest a token in the wrong collection440 // Create a token in another collection yet to be nested and try to nest505 const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT');441 const newToken = await collectionB.mintToken(alice);506 await expect(executeTransaction(442 await expect(newToken.nest(alice, targetToken))507 api, 508 alice, 509 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1),510 ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);443 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);444511 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address});445 expect(await targetToken.getChildren()).to.be.length(0);512 });446 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address});513 });447 });514448515 // ---------- Non-Fungible ----------449 // ---------- Non-Fungible ----------516450517 it('NFT: disallows to nest token if nesting is disabled', async () => {451 itSub('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {518 await usingApi(async api => {452 // Collection is implicitly not allowed nesting at creation519 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});453 const collection = await helper.nft.mintCollection(alice);520 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {}});521 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');454 const targetToken = await collection.mintToken(alice);522455523 // Try to create a nested token456 // Try to create a nested token as token owner when it's disallowed524 await expect(executeTransaction(api, alice, api.tx.unique.createItem(525 collection,457 await expect(collection.mintToken(alice, targetToken.nestingAddress()))526 {Ethereum: tokenIdToAddress(collection, targetToken)},527 {nft: {}} as any,458 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);528 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);529459530 // Create a token to be nested460 // Try to create a token to be nested and nest531 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');461 const newToken = await collection.mintToken(alice);532 // Try to nest462 await expect(newToken.nest(alice, targetToken))533 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);463 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);464534 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});465 expect(await targetToken.getChildren()).to.be.length(0);535 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});466 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address});536 });537 });467 });538468539 it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {469 itSub('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => {540 await usingApi(async api => {470 const collection = await helper.nft.mintCollection(alice);541 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});542 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});471 const targetToken = await collection.mintToken(alice);543472544 await addToAllowListExpectSuccess(alice, collection, bob.address);473 await collection.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}});545 await enableAllowListExpectSuccess(alice, collection);474 await collection.addToAllowList(alice, {Substrate: bob.address});546 await enablePublicMintingExpectSuccess(alice, collection);475 await collection.addToAllowList(alice, targetToken.nestingAddress());547476548 // Create a token to attempt to be nested into477 // Try to create a token to be nested and nest549 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');478 const newToken = await collection.mintToken(alice);479 await expect(newToken.nest(bob, targetToken)).to.be.rejectedWith(/common\.NoPermission/);550480551 // Try to create a nested token in the wrong collection481 expect(await targetToken.getChildren()).to.be.length(0);552 await expect(executeTransaction(api, alice, api.tx.unique.createItem(553 collection,554 {Ethereum: tokenIdToAddress(collection, targetToken)},555 {nft: {}} as any,556 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);557558 // Try to create and nest a token in the wrong collection482 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address});559 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');560 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);561 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});562 });563 });483 });564484565 it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {485 itSub('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => {566 await usingApi(async api => {486 const collection = await helper.nft.mintCollection(alice);567 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});568 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});487 const targetToken = await collection.mintToken(alice);569488570 await addToAllowListExpectSuccess(alice, collection, bob.address);489 await collection.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}});571 await enableAllowListExpectSuccess(alice, collection);490 await collection.addToAllowList(alice, {Substrate: bob.address});572 await enablePublicMintingExpectSuccess(alice, collection);491 await collection.addToAllowList(alice, targetToken.nestingAddress());573492574 // Create a token to attempt to be nested into493 const collectionB = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true}});575 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');494 await collectionB.addToAllowList(alice, {Substrate: bob.address});495 await collectionB.addToAllowList(alice, targetToken.nestingAddress());576496577 // Try to create a nested token in the wrong collection497 // Try to create a token to be nested and nest578 await expect(executeTransaction(api, alice, api.tx.unique.createItem(498 const newToken = await collectionB.mintToken(alice);579 collection,499 await expect(newToken.nest(bob, targetToken)).to.be.rejectedWith(/common\.NoPermission/);580 {Ethereum: tokenIdToAddress(collection, targetToken)},581 {nft: {}} as any,582 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);583500584 // Try to create and nest a token in the wrong collection501 expect(await targetToken.getChildren()).to.be.length(0);585 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');586 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);587 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});502 expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address});588 });589 });503 });590504591 it('NFT: disallows to nest token in an unlisted collection', async () => {505 itSub('NFT: disallows to nest token in an unlisted collection', async ({helper}) => {592 await usingApi(async api => {506 // Create collection with restricted nesting -- even self is not allowed593 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});507 const collection = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: []}}});594 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[]}});508 const targetToken = await collection.mintToken(alice, {Substrate: bob.address});595509596 // Create a token to attempt to be nested into510 await collection.addToAllowList(alice, {Substrate: bob.address});597 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');511 await collection.addToAllowList(alice, targetToken.nestingAddress());598512599 // Try to create a nested token in the wrong collection513 // Try to mint in own collection after allowlisting the accounts600 await expect(executeTransaction(api, alice, api.tx.unique.createItem(514 await expect(collection.mintToken(bob, targetToken.nestingAddress()))601 collection,602 {Ethereum: tokenIdToAddress(collection, targetToken)},603 {nft: {}} as any,604 )), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);605606 // Try to create and nest a token in the wrong collection607 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');608 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);515 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);609 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});610 });611 });516 });612517613 // ---------- Fungible ----------518 // ---------- Fungible ----------614519615 it('Fungible: disallows to nest token if nesting is disabled', async () => {520 itSub('Fungible: disallows to nest token if nesting is disabled', async ({helper}) => {616 await usingApi(async api => {521 const collectionNFT = await helper.nft.mintCollection(alice);617 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});618 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});619 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');522 const collectionFT = await helper.ft.mintCollection(alice);620 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};523 const targetToken = await collectionNFT.mintToken(alice);621524622 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});525 // Try to create an immediately nested token526 await expect(collectionFT.mint(alice, 5n, targetToken.nestingAddress()))527 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);623528624 // Try to create a nested token529 // Try to create a token to be nested and nest625 await expect(executeTransaction(api, alice, api.tx.unique.createItem(626 collectionFT,627 targetAddress,628 {Fungible: {Value: 10}},629 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);630631 // Create a token to be nested632 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');530 await collectionFT.mint(alice, 5n);633 // Try to nest531 await expect(collectionFT.transfer(alice, targetToken.nestingAddress(), 2n))634 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);532 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);635636 // Create another token to be nested533 expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(5n);637 const newToken2 = await createItemExpectSuccess(alice, collectionFT, 'Fungible');638 // Try to nest inside a fungible token639 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionFT, newToken)}, collectionFT, newToken2, 1)), 'while nesting new token inside fungible').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);640 });641 });534 });642535643 it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {536 itSub('Fungible: disallows a non-Owner to unnest someone else\'s token', async ({helper}) => {644 await usingApi(async api => {537 const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true}}});645 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});538 const collectionFT = await helper.ft.mintCollection(alice);646 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});539 const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address});647540648 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);541 // Nest some tokens as Alice into Bob's token649 await enableAllowListExpectSuccess(alice, collectionNFT);650 await enablePublicMintingExpectSuccess(alice, collectionNFT);542 await collectionFT.mint(alice, 5n, targetToken.nestingAddress());651543652 // Create a token to attempt to be nested into544 // Try to pull it out653 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');654 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};655656 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});657658 // Try to create a nested token in the wrong collection659 await expect(executeTransaction(api, alice, api.tx.unique.createItem(545 await expect(collectionFT.transferFrom(alice, targetToken.nestingAddress(), {Substrate: bob.address}, 1n))660 collectionFT,661 targetAddress,662 {Fungible: {Value: 10}},663 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);546 .to.be.rejectedWith(/common\.ApprovedValueTooLow/);664665 // Try to create and nest a token in the wrong collection547 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(5n);666 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');667 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);668 });669 });548 });670549671 it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {550 itSub('Fungible: disallows a non-Owner to unnest someone else\'s token (Restricted nesting)', async ({helper}) => {672 await usingApi(async api => {551 const collectionNFT = await helper.nft.mintCollection(alice);673 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});674 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);675 await enableAllowListExpectSuccess(alice, collectionNFT);552 const collectionFT = await helper.ft.mintCollection(alice);676 await enablePublicMintingExpectSuccess(alice, collectionNFT);553 const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address});677554678 // Create a token to attempt to be nested into555 await collectionNFT.setPermissions(alice, {nesting: {collectionAdmin: true, tokenOwner: true, restricted: [collectionFT.collectionId]}});679 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');680 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};681556682 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});557 // Nest some tokens as Alice into Bob's token683 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}});558 await collectionFT.mint(alice, 5n, targetToken.nestingAddress());684559685 // Try to create a nested token in the wrong collection560 // Try to pull it out as Alice still686 await expect(executeTransaction(api, alice, api.tx.unique.createItem(561 await expect(collectionFT.transferFrom(alice, targetToken.nestingAddress(), {Substrate: bob.address}, 1n))687 collectionFT,688 targetAddress,689 {Fungible: {Value: 10}},690 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);562 .to.be.rejectedWith(/common\.ApprovedValueTooLow/);691692 // Try to create and nest a token in the wrong collection563 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(5n);693 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');694 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);695 });696 });564 });697565698 it('Fungible: disallows to nest token in an unlisted collection', async () => {566 itSub('Fungible: disallows to nest token in an unlisted collection', async ({helper}) => {699 await usingApi(async api => {567 const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true, restricted: []}}});700 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});701 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});568 const collectionFT = await helper.ft.mintCollection(alice);569 const targetToken = await collectionNFT.mintToken(alice);702570703 // Create a token to attempt to be nested into571 // Try to mint an immediately nested token704 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');572 await expect(collectionFT.mint(alice, 5n, targetToken.nestingAddress()))705 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};573 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);706574707 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});575 // Mint a token and try to nest it576 await collectionFT.mint(alice, 5n);577 await expect(collectionFT.transfer(alice, targetToken.nestingAddress(), 1n))578 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);708579709 // Try to create a nested token in the wrong collection580 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(0n);710 await expect(executeTransaction(api, alice, api.tx.unique.createItem(711 collectionFT,712 targetAddress,713 {Fungible: {Value: 10}},714 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);715716 // Try to create and nest a token in the wrong collection581 expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(5n);717 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');718 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);719 });720 });582 });721583722 // ---------- Re-Fungible ----------584 // ---------- Re-Fungible ----------723585724 it('ReFungible: disallows to nest token if nesting is disabled', async function() {586 itSub.ifWithPallets('ReFungible: disallows to nest token if nesting is disabled', [Pallets.ReFungible], async ({helper}) => {725 await requirePallets(this, [Pallets.ReFungible]);587 const collectionNFT = await helper.nft.mintCollection(alice);588 const collectionRFT = await helper.rft.mintCollection(alice);589 const targetToken = await collectionNFT.mintToken(alice);726590727 await usingApi(async api => {591 // Try to create an immediately nested token728 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});729 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});592 await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAddress()))730 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');731 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};593 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);732594733 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});595 // Try to create a token to be nested and nest734735 // Create a nested token736 await expect(executeTransaction(api, alice, api.tx.unique.createItem(737 collectionRFT,738 targetAddress,739 {ReFungible: {pieces: 100}},740 )), 'while creating a nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);741742 // Create a token to be nested743 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');596 const token = await collectionRFT.mintToken(alice, 5n);744 // Try to nest745 await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);746 // Try to nest597 await expect(token.transfer(alice, targetToken.nestingAddress(), 2n))747 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);598 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);748749 // Create another token to be nested599 expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n);750 const newToken2 = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');751 // Try to nest inside a fungible token752 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionRFT, newToken)}, collectionRFT, newToken2, 1)), 'while nesting new token inside refungible').to.be.rejectedWith(/refungible\.RefungibleDisallowsNesting/);753 });754 });600 });755601756 it('ReFungible: disallows a non-Owner to nest someone else\'s token', async function() {602 itSub.ifWithPallets('ReFungible: disallows a non-Owner to nest someone else\'s token', [Pallets.ReFungible], async ({helper}) => {757 await requirePallets(this, [Pallets.ReFungible]);603 const collectionNFT = await helper.nft.mintCollection(alice);604 const collectionRFT = await helper.rft.mintCollection(alice);605 const targetToken = await collectionNFT.mintToken(alice);758606759 await usingApi(async api => {607 await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}});760 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});608 await collectionNFT.addToAllowList(alice, {Substrate: bob.address});761 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});609 await collectionNFT.addToAllowList(alice, targetToken.nestingAddress());762610763 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);611 // Try to create a token to be nested and nest764 await enableAllowListExpectSuccess(alice, collectionNFT);612 const newToken = await collectionRFT.mintToken(alice);765 await enablePublicMintingExpectSuccess(alice, collectionNFT);613 await expect(newToken.transfer(bob, targetToken.nestingAddress())).to.be.rejectedWith(/common\.TokenValueTooLow/);766614767 // Create a token to attempt to be nested into615 expect(await targetToken.getChildren()).to.be.length(0);768 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');769 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};616 expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n);770617771 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});618 // Nest some tokens as Alice into Bob's token619 await newToken.transfer(alice, targetToken.nestingAddress());772620773 // Try to create a nested token in the wrong collection621 // Try to pull it out774 await expect(executeTransaction(api, alice, api.tx.unique.createItem(622 await expect(newToken.transferFrom(bob, targetToken.nestingAddress(), {Substrate: alice.address}, 1n))775 collectionRFT,776 targetAddress,777 {ReFungible: {pieces: 100}},778 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);623 .to.be.rejectedWith(/common\.ApprovedValueTooLow/);779780 // Try to create and nest a token in the wrong collection624 expect(await newToken.getBalance(targetToken.nestingAddress())).to.be.equal(1n);781 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');782 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);783 });784 });625 });785626786 it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async function() {627 itSub.ifWithPallets('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', [Pallets.ReFungible], async ({helper}) => {787 await requirePallets(this, [Pallets.ReFungible]);628 const collectionNFT = await helper.nft.mintCollection(alice);629 const collectionRFT = await helper.rft.mintCollection(alice);630 const targetToken = await collectionNFT.mintToken(alice);788631789 await usingApi(async api => {632 await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: [collectionRFT.collectionId]}});790 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});791 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);633 await collectionNFT.addToAllowList(alice, {Substrate: bob.address});792 await enableAllowListExpectSuccess(alice, collectionNFT);634 await collectionNFT.addToAllowList(alice, targetToken.nestingAddress());793 await enablePublicMintingExpectSuccess(alice, collectionNFT);794635795 // Create a token to attempt to be nested into636 // Try to create a token to be nested and nest796 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');637 const newToken = await collectionRFT.mintToken(alice);797 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};638 await expect(newToken.transfer(bob, targetToken.nestingAddress())).to.be.rejectedWith(/common\.TokenValueTooLow/);798639799 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});640 expect(await targetToken.getChildren()).to.be.length(0);800 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});641 expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n);801642802 // Try to create a nested token in the wrong collection643 // Nest some tokens as Alice into Bob's token803 await expect(executeTransaction(api, alice, api.tx.unique.createItem(644 await newToken.transfer(alice, targetToken.nestingAddress());804 collectionRFT,805 targetAddress,806 {ReFungible: {pieces: 100}},807 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);808645809 // Try to create and nest a token in the wrong collection646 // Try to pull it out810 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');647 await expect(newToken.transferFrom(bob, targetToken.nestingAddress(), {Substrate: alice.address}, 1n))811 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);648 .to.be.rejectedWith(/common\.ApprovedValueTooLow/);812 });649 expect(await newToken.getBalance(targetToken.nestingAddress())).to.be.equal(1n);813 });650 });814651815 it('ReFungible: disallows to nest token to an unlisted collection', async function() {652 itSub.ifWithPallets('ReFungible: disallows to nest token to an unlisted collection', [Pallets.ReFungible], async ({helper}) => {816 await requirePallets(this, [Pallets.ReFungible]);653 const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true, restricted: []}}});654 const collectionRFT = await helper.rft.mintCollection(alice);655 const targetToken = await collectionNFT.mintToken(alice);817656818 await usingApi(async api => {657 // Try to create an immediately nested token819 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});658 await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAddress()))820 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});659 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);821660822 // Create a token to attempt to be nested into661 // Try to create a token to be nested and nest823 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');662 const token = await collectionRFT.mintToken(alice, 5n);824 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};663 await expect(token.transfer(alice, targetToken.nestingAddress(), 2n))825826 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});827828 // Try to create a nested token in the wrong collection829 await expect(executeTransaction(api, alice, api.tx.unique.createItem(830 collectionRFT,831 targetAddress,832 {ReFungible: {pieces: 100}},833 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);664 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);834835 // Try to create and nest a token in the wrong collection665 expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n);836 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');837 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);838 });839 });666 });840});667});841668tests/src/nesting/properties.test.tsdiffbeforeafterboth1import {expect} from 'chai';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.162import usingApi, {executeTransaction} from '../substrate/substrate-api';17/*import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {18import {4 addCollectionAdminExpectSuccess,19 addCollectionAdminExpectSuccess,5 CollectionMode,20 CollectionMode,8 createItemExpectSuccess,23 createItemExpectSuccess,9 getCreateCollectionResult,24 getCreateCollectionResult,10 transferExpectSuccess,25 transferExpectSuccess,11 requirePallets,12 Pallets,13} from '../util/helpers';26} from '../util/helpers';*/14import {IKeyringPair} from '@polkadot/types/types';27import {IKeyringPair} from '@polkadot/types/types';15import {tokenIdToAddress} from '../eth/util/helpers';28import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';29import {UniqueCollectionBase, UniqueHelper, UniqueNFTCollection, UniqueNFTToken, UniqueRFTCollection, UniqueRFTToken} from '../util/playgrounds/unique';163017let alice: IKeyringPair;31// ---------- COLLECTION PROPERTIES18let bob: IKeyringPair;19let charlie: IKeyringPair;203221describe('Composite Properties Test', () => {33describe('Integration Test: Collection Properties', () => {34 let alice: IKeyringPair;35 let bob: IKeyringPair;3622 before(async () => {37 before(async () => {23 await usingApi(async (api, privateKeyWrapper) => {38 await usingPlaygrounds(async (helper, privateKey) => {24 alice = privateKeyWrapper('//Alice');39 const donor = privateKey('//Alice');25 bob = privateKeyWrapper('//Bob');40 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);26 });41 });27 });42 });284329 async function testMakeSureSuppliesRequired(mode: CollectionMode) {44 itSub('Properties are initially empty', async ({helper}) => {30 await usingApi(async api => {31 const collectionId = await createCollectionExpectSuccess({mode: mode});45 const collection = await helper.nft.mintCollection(alice);46 expect(await collection.getProperties()).to.be.empty;47 });324833 const collectionOption = await api.rpc.unique.collectionById(collectionId);49 async function testSetsPropertiesForCollection(collection: UniqueCollectionBase) {34 expect(collectionOption.isSome).to.be.true;35 let collection = collectionOption.unwrap();50 // As owner36 expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;51 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;37 expect(collection.properties.toHuman()).to.be.empty;385239 const propertyPermissions = [53 await collection.addAdmin(alice, {Substrate: bob.address});40 {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},41 {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},42 ];43 await expect(executeTransaction(44 api, 45 alice, 46 api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), 47 )).to.not.be.rejected;485449 const collectionProperties = [55 // As administrator50 {key: 'black_hole', value: 'LIGO'},56 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;51 {key: 'electron', value: 'come bond'}, 52 ];53 await expect(executeTransaction(54 api, 55 alice, 56 api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 57 )).to.not.be.rejected;585759 collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();58 const properties = await collection.getProperties();60 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);59 expect(properties).to.include.deep.members([61 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);60 {key: 'electron', value: 'come bond'},62 });61 {key: 'black_hole', value: ''},62 ]);63 }63 }646465 it('Makes sure collectionById supplies required fields for NFT', async () => {65 itSub('Sets properties for a NFT collection', async ({helper}) => {66 await testMakeSureSuppliesRequired({type: 'NFT'});66 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));67 });67 });686869 it('Makes sure collectionById supplies required fields for ReFungible', async function() {69 itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {70 await requirePallets(this, [Pallets.ReFungible]);70 await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));7172 await testMakeSureSuppliesRequired({type: 'ReFungible'});73 });71 });74});757273 async function testCheckValidNames(collection: UniqueCollectionBase) {76// ---------- COLLECTION PROPERTIES74 // alpha symbols75 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;777678describe('Integration Test: Collection Properties', () => {77 // numeric symbols79 before(async () => {78 await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;80 await usingApi(async (api, privateKeyWrapper) => {81 alice = privateKeyWrapper('//Alice');82 bob = privateKeyWrapper('//Bob');83 });84 });857986 it('Reads properties from a collection', async () => {80 // underscore symbol87 await usingApi(async api => {81 await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;88 const collection = await createCollectionExpectSuccess();89 const properties = (await api.query.common.collectionProperties(collection)).toJSON();90 expect(properties.map).to.be.empty;91 expect(properties.consumedSpace).to.equal(0);92 });93 });948283 // dash symbol84 await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;958596 async function testSetsPropertiesForCollection(mode: string) {86 // dot symbol97 await usingApi(async api => {87 await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;98 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));99 const {collectionId} = getCreateCollectionResult(events);10088101 // As owner89 const properties = await collection.getProperties();102 await expect(executeTransaction(90 expect(properties).to.include.deep.members([103 api, 91 {key: 'answer', value: ''},104 bob, 105 api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 92 {key: '451', value: ''},106 )).to.not.be.rejected;93 {key: 'black_hole', value: ''},107108 await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);109110 // As administrator111 await expect(executeTransaction(112 api, 113 alice, 114 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 115 )).to.not.be.rejected;116117 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();118 expect(properties).to.be.deep.equal([94 {key: '-', value: ''},119 {key: 'electron', value: 'come bond'},120 {key: 'black_hole', value: ''},95 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},121 ]);122 });96 ]);123 }97 }124 it('Sets properties for a NFT collection', async () => {125 await testSetsPropertiesForCollection('NFT');126 });127 it('Sets properties for a ReFungible collection', async function() {128 await requirePallets(this, [Pallets.ReFungible]);12998130 await testSetsPropertiesForCollection('ReFungible');99 itSub('Check valid names for NFT collection properties keys', async ({helper}) => {100 await testCheckValidNames(await helper.nft.mintCollection(alice));131 });101 });132102133 async function testCheckValidNames(mode: string) {103 itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {134 await usingApi(async api => {135 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));136 const {collectionId} = getCreateCollectionResult(events);137 138 // alpha symbols139 await expect(executeTransaction(140 api, 141 bob, 142 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 143 )).to.not.be.rejected;144 145 // numeric symbols146 await expect(executeTransaction(147 api, 148 bob, 149 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 150 )).to.not.be.rejected;151 152 // underscore symbol153 await expect(executeTransaction(154 api, 155 bob, 156 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 157 )).to.not.be.rejected;158 159 // dash symbol160 await expect(executeTransaction(161 api, 162 bob, 163 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 164 )).to.not.be.rejected;165 166 // underscore symbol167 await expect(executeTransaction(168 api, 169 bob, 170 api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 171 )).to.not.be.rejected;172 173 const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];174 const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();104 await testCheckValidNames(await helper.rft.mintCollection(alice));175 expect(properties).to.be.deep.equal([176 {key: 'alpha', value: ''},177 {key: '123', value: ''},178 {key: 'black_hole', value: ''},179 {key: 'semi-automatic', value: ''},180 {key: 'build.rs', value: ''},181 ]);182 });183 }184 it('Check valid names for NFT collection properties keys', async () => {185 await testCheckValidNames('NFT');186 });105 });187 it('Check valid names for ReFungible collection properties keys', async function() {188 await requirePallets(this, [Pallets.ReFungible]);189106190 await testCheckValidNames('ReFungible');107 async function testChangesProperties(collection: UniqueCollectionBase) {191 });108 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;192109193 async function testChangesProperties(mode: CollectionMode) {110 // Mutate the properties194 await usingApi(async api => {195 const collection = await createCollectionExpectSuccess({mode: mode});196 197 await expect(executeTransaction(198 api, 199 alice, 200 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 201 )).to.not.be.rejected;202 203 // Mutate the properties204 await expect(executeTransaction(111 await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;205 api, 112206 alice, 207 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 208 )).to.not.be.rejected;209 210 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();113 const properties = await collection.getProperties();211 expect(properties).to.be.deep.equal([114 expect(properties).to.include.deep.members([212 {key: 'electron', value: 'bonded'},115 {key: 'electron', value: 'come bond'},213 {key: 'black_hole', value: 'LIGO'},116 {key: 'black_hole', value: 'LIGO'},214 ]);117 ]);215 });216 }118 }119217 it('Changes properties of a NFT collection', async () => {120 itSub('Changes properties of a NFT collection', async ({helper}) => {218 await testChangesProperties({type: 'NFT'});121 await testChangesProperties(await helper.nft.mintCollection(alice));219 });122 });220 it('Changes properties of a ReFungible collection', async function() {221 await requirePallets(this, [Pallets.ReFungible]);222123223 await testChangesProperties({type: 'ReFungible'});124 itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {125 await testChangesProperties(await helper.rft.mintCollection(alice));224 });126 });225127226 async function testDeleteProperties(mode: CollectionMode) {128 async function testDeleteProperties(collection: UniqueCollectionBase) {227 await usingApi(async api => {129 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;228 const collection = await createCollectionExpectSuccess({mode: mode});130229 230 await expect(executeTransaction(231 api, 232 alice, 233 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 234 )).to.not.be.rejected;235 236 await expect(executeTransaction(131 await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;237 api, 132238 alice, 239 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 240 )).to.not.be.rejected;241 242 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();133 const properties = await collection.getProperties(['black_hole', 'electron']);243 expect(properties).to.be.deep.equal([134 expect(properties).to.be.deep.equal([244 {key: 'black_hole', value: 'LIGO'},135 {key: 'black_hole', value: 'LIGO'},245 ]);136 ]);246 }); 247 }137 }138248 it('Deletes properties of a NFT collection', async () => {139 itSub('Deletes properties of a NFT collection', async ({helper}) => {249 await testDeleteProperties({type: 'NFT'});140 await testDeleteProperties(await helper.nft.mintCollection(alice));250 });141 });251 it('Deletes properties of a ReFungible collection', async function() {252 await requirePallets(this, [Pallets.ReFungible]);253142254 await testDeleteProperties({type: 'ReFungible'});143 itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {144 await testDeleteProperties(await helper.rft.mintCollection(alice));255 });145 });256});146});257147258describe('Negative Integration Test: Collection Properties', () => {148describe('Negative Integration Test: Collection Properties', () => {149 let alice: IKeyringPair;150 let bob: IKeyringPair;151259 before(async () => {152 before(async () => {260 await usingApi(async (api, privateKeyWrapper) => {153 await usingPlaygrounds(async (helper, privateKey) => {261 alice = privateKeyWrapper('//Alice');154 const donor = privateKey('//Alice');262 bob = privateKeyWrapper('//Bob');155 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);263 });156 });264 });157 });265 158 266 async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {159 async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueCollectionBase) { 267 await usingApi(async api => {268 const collection = await createCollectionExpectSuccess({mode: mode});160 await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))269 270 await expect(executeTransaction(271 api, 272 bob, 273 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 274 )).to.be.rejectedWith(/common\.NoPermission/);161 .to.be.rejectedWith(/common\.NoPermission/);275 162276 const properties = (await api.query.common.collectionProperties(collection)).toJSON();163 expect(await collection.getProperties()).to.be.empty;277 expect(properties.map).to.be.empty;278 expect(properties.consumedSpace).to.equal(0);279 }); 280 }164 }165281 it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {166 itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {282 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});167 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));283 });168 });284 it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async function() {285 await requirePallets(this, [Pallets.ReFungible]);286169287 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});170 itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {171 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));288 });172 });289 173 290 async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {174 async function testFailsSetPropertiesThatExeedLimits(collection: UniqueCollectionBase) {291 await usingApi(async api => {175 const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();292 const collection = await createCollectionExpectSuccess({mode: mode});293 const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 294 176 295 // Mute the general tx parsing error, too many bytes to process177 // Mute the general tx parsing error, too many bytes to process296 {178 {297 console.error = () => {};179 console.error = () => {};298 await expect(executeTransaction(180 await expect(collection.setProperties(alice, [299 api, 300 alice, 301 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 181 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},302 )).to.be.rejected;182 ])).to.be.rejected;303 }183 }304 184305 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();185 expect(await collection.getProperties(['electron'])).to.be.empty;306 expect(properties).to.be.empty;186307 308 await expect(executeTransaction(187 await expect(collection.setProperties(alice, [309 api, 310 alice, 311 api.tx.unique.setCollectionProperties(collection, [312 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 188 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 313 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 189 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 314 ]), 190 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);315 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);191316 317 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();192 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;318 expect(properties).to.be.empty;319 }); 320 }193 }194321 it('Fails to set properties that exceed the limits (NFT)', async () => {195 itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => {322 await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});196 await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));323 });197 });324 it('Fails to set properties that exceed the limits (ReFungible)', async function() {325 await requirePallets(this, [Pallets.ReFungible]);326198327 await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});199 itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {200 await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));328 });201 });329 202 330 async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {203 async function testFailsSetMorePropertiesThanAllowed(collection: UniqueCollectionBase) {331 await usingApi(async api => {204 const propertiesToBeSet = [];332 const collection = await createCollectionExpectSuccess({mode: mode});333 334 const propertiesToBeSet = [];335 for (let i = 0; i < 65; i++) {205 for (let i = 0; i < 65; i++) {336 propertiesToBeSet.push({206 propertiesToBeSet.push({337 key: 'electron_' + i,207 key: 'electron_' + i,338 value: Math.random() > 0.5 ? 'high' : 'low',208 value: Math.random() > 0.5 ? 'high' : 'low',339 });209 });340 }210 }341 211342 await expect(executeTransaction(212 await expect(collection.setProperties(alice, propertiesToBeSet)).343 api, 344 alice, 345 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 346 )).to.be.rejectedWith(/common\.PropertyLimitReached/);213 to.be.rejectedWith(/common\.PropertyLimitReached/);347 214348 const properties = (await api.query.common.collectionProperties(collection)).toJSON();215 expect(await collection.getProperties()).to.be.empty;349 expect(properties.map).to.be.empty;350 expect(properties.consumedSpace).to.equal(0);351 }); 352 }216 }217353 it('Fails to set more properties than it is allowed (NFT)', async () => {218 itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {354 await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});219 await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));355 });220 });356 it('Fails to set more properties than it is allowed (ReFungible)', async function() {357 await requirePallets(this, [Pallets.ReFungible]);358221359 await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});222 itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {223 await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));360 });224 });361 225 362 async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {226 async function testFailsSetPropertiesWithInvalidNames(collection: UniqueCollectionBase) {363 await usingApi(async api => {227 const invalidProperties = [364 const collection = await createCollectionExpectSuccess({mode: mode});365 366 const invalidProperties = [367 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],228 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],368 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],229 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],369 [{key: 'déjà vu', value: 'hmm...'}],230 [{key: 'déjà vu', value: 'hmm...'}],370 ];231 ];371 232372 for (let i = 0; i < invalidProperties.length; i++) {233 for (let i = 0; i < invalidProperties.length; i++) {373 await expect(executeTransaction(234 await expect(374 api, 235 collection.setProperties(alice, invalidProperties[i]), 375 alice, 376 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 377 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);236 `on rejecting the new badly-named property #${i}`,237 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);378 }238 }379 239380 await expect(executeTransaction(240 await expect(381 api, 241 collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 382 alice, 383 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 384 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);242 'on rejecting an unnamed property',385 243 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);244386 await expect(executeTransaction(245 await expect(387 api, 246 collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 388 alice, 389 api.tx.unique.setCollectionProperties(collection, [390 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},391 ]), 392 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;247 'on setting the correctly-but-still-badly-named property',393 248 ).to.be.fulfilled;249394 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');250 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');395 251396 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();252 const properties = await collection.getProperties(keys);397 expect(properties).to.be.deep.equal([253 expect(properties).to.be.deep.equal([398 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},254 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},399 ]);255 ]);400 256401 for (let i = 0; i < invalidProperties.length; i++) {257 for (let i = 0; i < invalidProperties.length; i++) {402 await expect(executeTransaction(258 await expect(403 api, 259 collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), 404 alice, 405 api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 406 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);260 `on trying to delete the non-existent badly-named property #${i}`,261 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);407 }262 }408 });409 }263 }264410 it('Fails to set properties with invalid names (NFT)', async () => {265 itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => {411 await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});266 await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));412 });267 });413 it('Fails to set properties with invalid names (ReFungible)', async function() {414 await requirePallets(this, [Pallets.ReFungible]);415268416 await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});269 itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {270 await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));417 });271 });418});272});419273420// ---------- ACCESS RIGHTS274// ---------- ACCESS RIGHTS421275422describe('Integration Test: Access Rights to Token Properties', () => {276describe('Integration Test: Access Rights to Token Properties', () => {277 let alice: IKeyringPair;278 let bob: IKeyringPair;279423 before(async () => {280 before(async () => {424 await usingApi(async (api, privateKeyWrapper) => {281 await usingPlaygrounds(async (helper, privateKey) => {425 alice = privateKeyWrapper('//Alice');282 const donor = privateKey('//Alice');426 bob = privateKeyWrapper('//Bob');283 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);427 });284 });428 });285 });429 286 430 it('Reads access rights to properties of a collection', async () => {287 itSub('Reads access rights to properties of a collection', async ({helper}) => {431 await usingApi(async api => {432 const collection = await createCollectionExpectSuccess();288 const collection = await helper.nft.mintCollection(alice);433 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();289 const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON();434 expect(propertyRights).to.be.empty;290 expect(propertyRights).to.be.empty;435 });436 });291 });437 292 438 async function testSetsAccessRightsToProperties(mode: CollectionMode) {293 async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 439 await usingApi(async api => {440 const collection = await createCollectionExpectSuccess({mode: mode});441 294 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}]))442 await expect(executeTransaction(443 api, 444 alice, 445 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 446 )).to.not.be.rejected;295 .to.be.fulfilled;447 296448 await addCollectionAdminExpectSuccess(alice, collection, bob.address);297 await collection.addAdmin(alice, {Substrate: bob.address});449 298450 await expect(executeTransaction(299 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]))451 api, 452 alice, 453 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 454 )).to.not.be.rejected;300 .to.be.fulfilled;455 301456 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();302 const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);457 expect(propertyRights).to.be.deep.equal([303 expect(propertyRights).to.include.deep.members([458 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},304 {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},459 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},305 {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},460 ]);306 ]);461 }); 462 }307 }308463 it('Sets access rights to properties of a collection (NFT)', async () => {309 itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => {464 await testSetsAccessRightsToProperties({type: 'NFT'});310 await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));465 });311 });466 it('Sets access rights to properties of a collection (ReFungible)', async function() {467 await requirePallets(this, [Pallets.ReFungible]);468312469 await testSetsAccessRightsToProperties({type: 'ReFungible'});313 itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {314 await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));470 });315 });471 316 472 async function testChangesAccessRightsToProperty(mode: CollectionMode) {317 async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) {473 await usingApi(async api => {474 const collection = await createCollectionExpectSuccess({mode: mode});318 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]))475 476 await expect(executeTransaction(477 api, 478 alice, 479 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 480 )).to.not.be.rejected;319 .to.be.fulfilled;481 320482 await expect(executeTransaction(321 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))483 api, 484 alice, 485 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 486 )).to.not.be.rejected;322 .to.be.fulfilled;487 323488 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();324 const propertyRights = await collection.getPropertyPermissions();489 expect(propertyRights).to.be.deep.equal([325 expect(propertyRights).to.be.deep.equal([490 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},326 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},491 ]);327 ]);492 });493 }328 }329494 it('Changes access rights to properties of a NFT collection', async () => {330 itSub('Changes access rights to properties of a NFT collection', async ({helper}) => {495 await testChangesAccessRightsToProperty({type: 'NFT'});331 await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));496 });332 });497 it('Changes access rights to properties of a ReFungible collection', async function() {498 await requirePallets(this, [Pallets.ReFungible]);499333500 await testChangesAccessRightsToProperty({type: 'ReFungible'});334 itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {335 await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));501 });336 });502});337});503338504describe('Negative Integration Test: Access Rights to Token Properties', () => {339describe('Negative Integration Test: Access Rights to Token Properties', () => {340 let alice: IKeyringPair;341 let bob: IKeyringPair;342505 before(async () => {343 before(async () => {506 await usingApi(async (api, privateKeyWrapper) => {344 await usingPlaygrounds(async (helper, privateKey) => {507 alice = privateKeyWrapper('//Alice');345 const donor = privateKey('//Alice');508 bob = privateKeyWrapper('//Bob');346 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);509 });347 });510 });348 });511349512 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {350 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) {513 await usingApi(async api => {514 const collection = await createCollectionExpectSuccess({mode: mode});351 await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]))515 516 await expect(executeTransaction(517 api, 518 bob, 519 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 520 )).to.be.rejectedWith(/common\.NoPermission/);352 .to.be.rejectedWith(/common\.NoPermission/);521 353522 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();354 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);523 expect(propertyRights).to.be.empty;355 expect(propertyRights).to.be.empty;524 });525 }356 }357526 it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {358 itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) => {527 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});359 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));528 });360 });529 it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async function() {530 await requirePallets(this, [Pallets.ReFungible]);531361532 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});362 itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {363 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));533 });364 });534365535 async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {366 async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 536 await usingApi(async api => {537 const collection = await createCollectionExpectSuccess({mode: mode});538 367 const constitution = [];539 const constitution = [];540 for (let i = 0; i < 65; i++) {368 for (let i = 0; i < 65; i++) {541 constitution.push({369 constitution.push({542 key: 'property_' + i,370 key: 'property_' + i,543 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},371 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},544 });372 });545 }373 }546 374547 await expect(executeTransaction(375 await expect(collection.setTokenPropertyPermissions(alice, constitution))548 api, 549 alice, 550 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 551 )).to.be.rejectedWith(/common\.PropertyLimitReached/);376 .to.be.rejectedWith(/common\.PropertyLimitReached/);552 377553 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();378 const propertyRights = await collection.getPropertyPermissions();554 expect(propertyRights).to.be.empty;379 expect(propertyRights).to.be.empty;555 }); 556 }380 }381557 it('Prevents from adding too many possible properties (NFT)', async () => {382 itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => {558 await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});383 await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));559 });384 });560 it('Prevents from adding too many possible properties (ReFungible)', async function() {561 await requirePallets(this, [Pallets.ReFungible]);562385563 await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});386 itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {387 await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));564 });388 });565389566 async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {390 async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) {567 await usingApi(async api => {568 const collection = await createCollectionExpectSuccess({mode: mode});391 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))569 570 await expect(executeTransaction(571 api, 572 alice, 573 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 574 )).to.not.be.rejected;392 .to.be.fulfilled;575 393576 await expect(executeTransaction(394 await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}]))577 api, 578 alice, 579 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 580 )).to.be.rejectedWith(/common\.NoPermission/);395 .to.be.rejectedWith(/common\.NoPermission/);581 396582 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();397 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);583 expect(propertyRights).to.deep.equal([398 expect(propertyRights).to.deep.equal([584 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},399 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},585 ]);400 ]);586 }); 587 }401 }402588 it('Prevents access rights to be modified if constant (NFT)', async () => {403 itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => {589 await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});404 await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));590 });405 });591 it('Prevents access rights to be modified if constant (ReFungible)', async function() {592 await requirePallets(this, [Pallets.ReFungible]);593406594 await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});407 itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {408 await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));595 });409 });596410597 async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {411 async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) {598 await usingApi(async api => {599 const collection = await createCollectionExpectSuccess({mode: mode});412 const invalidProperties = [600 601 const invalidProperties = [602 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],413 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],603 [{key: 'G#4', permission: {tokenOwner: true}}],414 [{key: 'G#4', permission: {tokenOwner: true}}],604 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],415 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],605 ];416 ];606 417607 for (let i = 0; i < invalidProperties.length; i++) {418 for (let i = 0; i < invalidProperties.length; i++) {608 await expect(executeTransaction(419 await expect(609 api, 420 collection.setTokenPropertyPermissions(alice, invalidProperties[i]), 610 alice, 611 api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 612 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);421 `on setting the new badly-named property #${i}`,422 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);613 }423 }614 424615 await expect(executeTransaction(425 await expect(616 api, 426 collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 617 alice, 618 api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 619 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);427 'on rejecting an unnamed property',620 428 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);429621 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string430 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string622 await expect(executeTransaction(431 await expect(623 api, 432 collection.setTokenPropertyPermissions(alice, [624 alice, 625 api.tx.unique.setTokenPropertyPermissions(collection, [626 {key: correctKey, permission: {collectionAdmin: true}},433 {key: correctKey, permission: {collectionAdmin: true}},627 ]), 434 ]), 628 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;435 'on setting the correctly-but-still-badly-named property',629 436 ).to.be.fulfilled;437630 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');438 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');631 439632 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();440 const propertyRights = await collection.getPropertyPermissions(keys);633 expect(propertyRights).to.be.deep.equal([441 expect(propertyRights).to.be.deep.equal([634 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},442 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},635 ]);443 ]);636 });637 }444 }445638 it('Prevents adding properties with invalid names (NFT)', async () => {446 itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => {639 await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});447 await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));640 });448 });641 it('Prevents adding properties with invalid names (ReFungible)', async function() {642 await requirePallets(this, [Pallets.ReFungible]);643449644 await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});450 itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {451 await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));645 });452 });646});453});647454648// ---------- TOKEN PROPERTIES455// ---------- TOKEN PROPERTIES649456650describe('Integration Test: Token Properties', () => {457describe('Integration Test: Token Properties', () => {458 let alice: IKeyringPair; // collection owner459 let bob: IKeyringPair; // collection admin460 let charlie: IKeyringPair; // token owner461651 let permissions: {permission: any, signers: IKeyringPair[]}[];462 let permissions: {permission: any, signers: IKeyringPair[]}[];652463653 before(async () => {464 before(async () => {654 await usingApi(async (api, privateKeyWrapper) => {465 await usingPlaygrounds(async (helper, privateKey) => {655 alice = privateKeyWrapper('//Alice'); // collection owner466 const donor = privateKey('//Alice');656 bob = privateKeyWrapper('//Bob'); // collection admin467 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);657 charlie = privateKeyWrapper('//Charlie'); // token owner658659 permissions = [660 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},661 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},662 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},663 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},664 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},665 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},666 ];667 });468 });469470 // todo:playgrounds probably separate these tests later471 permissions = [472 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},473 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},474 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},475 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},476 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},477 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},478 ];668 });479 });669 480 670 async function testReadsYetEmptyProperties(mode: CollectionMode) {481 async function testReadsYetEmptyProperties(token: UniqueNFTToken | UniqueRFTToken) {671 await usingApi(async api => {672 const collection = await createCollectionExpectSuccess({mode: mode});482 const properties = await token.getProperties();673 const token = await createItemExpectSuccess(alice, collection, mode.type);674 675 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();676 expect(properties.map).to.be.empty;483 expect(properties).to.be.empty;677 expect(properties.consumedSpace).to.be.equal(0);484678 485 const tokenData = await token.getData();679 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;680 expect(tokenData).to.be.empty;486 expect(tokenData!.properties).to.be.empty;681 });682 }487 }488683 it('Reads yet empty properties of a token (NFT)', async () => {489 itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {684 await testReadsYetEmptyProperties({type: 'NFT'});490 const collection = await helper.nft.mintCollection(alice);491 const token = await collection.mintToken(alice);492 await testReadsYetEmptyProperties(token);685 });493 });686 it('Reads yet empty properties of a token (ReFungible)', async function() {687 await requirePallets(this, [Pallets.ReFungible]);688494689 await testReadsYetEmptyProperties({type: 'ReFungible'});495 itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {496 const collection = await helper.rft.mintCollection(alice);497 const token = await collection.mintToken(alice);498 await testReadsYetEmptyProperties(token);690 });499 });691500692 async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {501 async function testAssignPropertiesAccordingToPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {693 await usingApi(async api => {502 await token.collection.addAdmin(alice, {Substrate: bob.address});694 const collection = await createCollectionExpectSuccess({mode: mode});695 const token = await createItemExpectSuccess(alice, collection, mode.type);696 await addCollectionAdminExpectSuccess(alice, collection, bob.address);697 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);503 await token.transfer(alice, {Substrate: charlie.address}, pieces);698504699 const propertyKeys: string[] = [];505 const propertyKeys: string[] = [];700 let i = 0;506 let i = 0;701 for (const permission of permissions) {507 for (const permission of permissions) {702 for (const signer of permission.signers) {508 i++;509 let j = 0;510 for (const signer of permission.signers) {703 const key = i + '_' + signer.address;511 j++;512 const key = i + '_' + signer.address;704 propertyKeys.push(key);513 propertyKeys.push(key);705514706 await expect(executeTransaction(515 await expect(707 api, 516 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 708 alice, 709 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 710 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;517 `on setting permission #${i} by alice`,518 ).to.be.fulfilled;711519712 await expect(executeTransaction(520 await expect(713 api, 521 token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), 714 signer, 715 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 716 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;522 `on adding property #${i} by signer #${j}`,717 }523 ).to.be.fulfilled;718719 i++;720 }524 }525 }721526722 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];527 const properties = await token.getProperties(propertyKeys);723 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];528 const tokenData = await token.getData();724 for (let i = 0; i < properties.length; i++) {529 for (let i = 0; i < properties.length; i++) {725 expect(properties[i].value).to.be.equal('Serotonin increase');530 expect(properties[i].value).to.be.equal('Serotonin increase');726 expect(tokensData[i].value).to.be.equal('Serotonin increase');531 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');727 }532 }728 });729 }533 }534730 it('Assigns properties to a token according to permissions (NFT)', async () => {535 itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) => {731 await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);536 const collection = await helper.nft.mintCollection(alice);537 const token = await collection.mintToken(alice);538 await testAssignPropertiesAccordingToPermissions(token, 1n);732 });539 });733 it('Assigns properties to a token according to permissions (ReFungible)', async function() {734 await requirePallets(this, [Pallets.ReFungible]);735540736 await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);541 itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {542 const collection = await helper.rft.mintCollection(alice);543 const token = await collection.mintToken(alice, 100n);544 await testAssignPropertiesAccordingToPermissions(token, 100n);737 });545 });738546739 async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {547 async function testChangesPropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {740 await usingApi(async api => {548 await token.collection.addAdmin(alice, {Substrate: bob.address});741 const collection = await createCollectionExpectSuccess({mode: mode});742 const token = await createItemExpectSuccess(alice, collection, mode.type);743 await addCollectionAdminExpectSuccess(alice, collection, bob.address);744 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);549 await token.transfer(alice, {Substrate: charlie.address}, pieces);745550746 const propertyKeys: string[] = [];551 const propertyKeys: string[] = [];747 let i = 0;552 let i = 0;748 for (const permission of permissions) {553 for (const permission of permissions) {749 if (!permission.permission.mutable) continue;554 i++;555 if (!permission.permission.mutable) continue;750 556 751 for (const signer of permission.signers) {557 let j = 0;558 for (const signer of permission.signers) {752 const key = i + '_' + signer.address;559 j++;560 const key = i + '_' + signer.address;753 propertyKeys.push(key);561 propertyKeys.push(key);754 562755 await expect(executeTransaction(563 await expect(756 api, 564 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 757 alice, 758 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 759 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;565 `on setting permission #${i} by alice`,760 566 ).to.be.fulfilled;567761 await expect(executeTransaction(568 await expect(762 api, 569 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 763 signer, 764 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 765 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;570 `on adding property #${i} by signer #${j}`,766 571 ).to.be.fulfilled;572767 await expect(executeTransaction(573 await expect(768 api, 574 token.setProperties(signer, [{key, value: 'Serotonin stable'}]), 769 signer, 770 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 771 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;575 `on changing property #${i} by signer #${j}`,772 }576 ).to.be.fulfilled;773 774 i++;775 }577 }776 578 }579777 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];580 const properties = await token.getProperties(propertyKeys);778 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];581 const tokenData = await token.getData();779 for (let i = 0; i < properties.length; i++) {582 for (let i = 0; i < properties.length; i++) {780 expect(properties[i].value).to.be.equal('Serotonin stable');583 expect(properties[i].value).to.be.equal('Serotonin stable');781 expect(tokensData[i].value).to.be.equal('Serotonin stable');584 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');782 }585 }783 });784 }586 }587785 it('Changes properties of a token according to permissions (NFT)', async () => {588 itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) => {786 await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);589 const collection = await helper.nft.mintCollection(alice);590 const token = await collection.mintToken(alice);591 await testChangesPropertiesAccordingPermission(token, 1n);787 });592 });788 it('Changes properties of a token according to permissions (ReFungible)', async function() {789 await requirePallets(this, [Pallets.ReFungible]);790593791 await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);594 itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {595 const collection = await helper.rft.mintCollection(alice);596 const token = await collection.mintToken(alice, 100n);597 await testChangesPropertiesAccordingPermission(token, 100n);792 });598 });793599794 async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {600 async function testDeletePropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {795 await usingApi(async api => {601 await token.collection.addAdmin(alice, {Substrate: bob.address});796 const collection = await createCollectionExpectSuccess({mode: mode});797 const token = await createItemExpectSuccess(alice, collection, mode.type);798 await addCollectionAdminExpectSuccess(alice, collection, bob.address);799 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);602 await token.transfer(alice, {Substrate: charlie.address}, pieces);800603801 const propertyKeys: string[] = [];604 const propertyKeys: string[] = [];802 let i = 0;605 let i = 0;803 606804 for (const permission of permissions) {607 for (const permission of permissions) {805 if (!permission.permission.mutable) continue;608 i++;609 if (!permission.permission.mutable) continue;806 610 807 for (const signer of permission.signers) {611 let j = 0;612 for (const signer of permission.signers) {808 const key = i + '_' + signer.address;613 j++;614 const key = i + '_' + signer.address;809 propertyKeys.push(key);615 propertyKeys.push(key);810 616811 await expect(executeTransaction(617 await expect(812 api, 618 token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 813 alice, 814 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 815 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;619 `on setting permission #${i} by alice`,816 620 ).to.be.fulfilled;621817 await expect(executeTransaction(622 await expect(818 api, 623 token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 819 signer, 820 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 821 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;624 `on adding property #${i} by signer #${j}`,822 625 ).to.be.fulfilled;626823 await expect(executeTransaction(627 await expect(824 api, 628 token.deleteProperties(signer, [key]), 825 signer, 826 api.tx.unique.deleteTokenProperties(collection, token, [key]), 827 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;629 `on deleting property #${i} by signer #${j}`,828 }630 ).to.be.fulfilled;829 830 i++;831 }631 }832 632 }833 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];633834 expect(properties).to.be.empty;634 expect(await token.getProperties(propertyKeys)).to.be.empty;835 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];836 expect(tokensData).to.be.empty;837 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);635 expect((await token.getData())!.properties).to.be.empty;838 });839 }636 }840 it('Deletes properties of a token according to permissions (NFT)', async () => {637 638 itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => {841 await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);639 const collection = await helper.nft.mintCollection(alice);640 const token = await collection.mintToken(alice);641 await testDeletePropertiesAccordingPermission(token, 1n);842 });642 });843 it('Deletes properties of a token according to permissions (ReFungible)', async function() {844 await requirePallets(this, [Pallets.ReFungible]);845643846 await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);644 itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {645 const collection = await helper.rft.mintCollection(alice);646 const token = await collection.mintToken(alice, 100n);647 await testDeletePropertiesAccordingPermission(token, 100n);847 });648 });848649849 it('Assigns properties to a nested token according to permissions', async () => {650 itSub('Assigns properties to a nested token according to permissions', async ({helper}) => {850 await usingApi(async api => {851 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});651 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});852 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});853 const token = await createItemExpectSuccess(alice, collection, 'NFT');652 const collectionB = await helper.nft.mintCollection(alice);854 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});653 const targetToken = await collectionA.mintToken(alice);855 await addCollectionAdminExpectSuccess(alice, collection, bob.address);654 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAddress());856 await transferExpectSuccess(collection, token, alice, charlie);857655858 const propertyKeys: string[] = [];656 await collectionB.addAdmin(alice, {Substrate: bob.address});859 let i = 0;860 for (const permission of permissions) {861 for (const signer of permission.signers) {862 const key = i + '_' + signer.address;657 await targetToken.transfer(alice, {Substrate: charlie.address});863 propertyKeys.push(key);864658865 await expect(executeTransaction(659 const propertyKeys: string[] = [];660 let i = 0;661 for (const permission of permissions) {662 i++;663 let j = 0;664 for (const signer of permission.signers) {866 api, 665 j++;666 const key = i + '_' + signer.address;867 alice, 667 propertyKeys.push(key);668 868 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 669 await expect(670 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 869 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;671 `on setting permission #${i} by alice`,672 ).to.be.fulfilled;870673871 await expect(executeTransaction(674 await expect(872 api, 675 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 873 signer, 874 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 875 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;676 `on adding property #${i} by signer #${j}`,876 }677 ).to.be.fulfilled;877878 i++;879 }678 }880679881 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];680 }681682 const properties = await nestedToken.getProperties(propertyKeys);882 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];683 const tokenData = await nestedToken.getData();883 for (let i = 0; i < properties.length; i++) {684 for (let i = 0; i < properties.length; i++) {884 expect(properties[i].value).to.be.equal('Serotonin increase');685 expect(properties[i].value).to.be.equal('Serotonin increase');885 expect(tokensData[i].value).to.be.equal('Serotonin increase');686 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');886 }687 }887 });688 expect(await targetToken.getProperties()).to.be.empty;888 });689 });889690890 it('Changes properties of a nested token according to permissions', async () => {691 itSub('Changes properties of a nested token according to permissions', async ({helper}) => {891 await usingApi(async api => {892 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});692 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});893 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});894 const token = await createItemExpectSuccess(alice, collection, 'NFT');693 const collectionB = await helper.nft.mintCollection(alice);895 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});694 const targetToken = await collectionA.mintToken(alice);896 await addCollectionAdminExpectSuccess(alice, collection, bob.address);695 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAddress());897 await transferExpectSuccess(collection, token, alice, charlie);898696899 const propertyKeys: string[] = [];697 await collectionB.addAdmin(alice, {Substrate: bob.address});900 let i = 0;901 for (const permission of permissions) {902 if (!permission.permission.mutable) continue;903 698 await targetToken.transfer(alice, {Substrate: charlie.address});904 for (const signer of permission.signers) {905 const key = i + '_' + signer.address;906 propertyKeys.push(key);907699908 await expect(executeTransaction(700 const propertyKeys: string[] = [];701 let i = 0;909 api, 702 for (const permission of permissions) {703 i++;910 alice, 704 if (!permission.permission.mutable) continue;705 911 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 706 let j = 0;707 for (const signer of permission.signers) {912 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;708 j++;709 const key = i + '_' + signer.address;710 propertyKeys.push(key);913711914 await expect(executeTransaction(712 await expect(915 api, 713 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 916 signer, 917 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 918 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;714 `on setting permission #${i} by alice`,715 ).to.be.fulfilled;919716920 await expect(executeTransaction(717 await expect(921 api, 718 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 922 signer, 923 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), 924 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;719 `on adding property #${i} by signer #${j}`,925 }720 ).to.be.fulfilled;926721927 i++;722 await expect(723 nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 724 `on changing property #${i} by signer #${j}`,725 ).to.be.fulfilled;928 }726 }727 }929728930 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];729 const properties = await nestedToken.getProperties(propertyKeys);931 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];730 const tokenData = await nestedToken.getData();932 for (let i = 0; i < properties.length; i++) {731 for (let i = 0; i < properties.length; i++) {933 expect(properties[i].value).to.be.equal('Serotonin stable');732 expect(properties[i].value).to.be.equal('Serotonin stable');934 expect(tokensData[i].value).to.be.equal('Serotonin stable');733 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');935 }734 }936 });735 expect(await targetToken.getProperties()).to.be.empty;937 });736 });938737939 it('Deletes properties of a nested token according to permissions', async () => {738 itSub('Deletes properties of a nested token according to permissions', async ({helper}) => {940 await usingApi(async api => {941 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});739 const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});942 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});943 const token = await createItemExpectSuccess(alice, collection, 'NFT');740 const collectionB = await helper.nft.mintCollection(alice);944 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});741 const targetToken = await collectionA.mintToken(alice);945 await addCollectionAdminExpectSuccess(alice, collection, bob.address);742 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAddress());946 await transferExpectSuccess(collection, token, alice, charlie);947743948 const propertyKeys: string[] = [];744 await collectionB.addAdmin(alice, {Substrate: bob.address});949 let i = 0;745 await targetToken.transfer(alice, {Substrate: charlie.address});950746951 for (const permission of permissions) {747 const propertyKeys: string[] = [];748 let i = 0;749 for (const permission of permissions) {952 if (!permission.permission.mutable) continue;750 i++;751 if (!permission.permission.mutable) continue;953 752 954 for (const signer of permission.signers) {753 let j = 0;754 for (const signer of permission.signers) {955 const key = i + '_' + signer.address;755 j++;756 const key = i + '_' + signer.address;956 propertyKeys.push(key);757 propertyKeys.push(key);957758958 await expect(executeTransaction(759 await expect(959 api, 760 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 960 alice, 961 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 962 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;761 `on setting permission #${i} by alice`,762 ).to.be.fulfilled;963763964 await expect(executeTransaction(764 await expect(965 api, 765 nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 966 signer, 967 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 968 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;766 `on adding property #${i} by signer #${j}`,767 ).to.be.fulfilled;969768970 await expect(executeTransaction(769 await expect(971 api, 770 nestedToken.deleteProperties(signer, [key]), 972 signer, 973 api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), 974 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;771 `on deleting property #${i} by signer #${j}`,975 }772 ).to.be.fulfilled;976 977 i++;978 }773 }774 }979775980 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];776 expect(await nestedToken.getProperties(propertyKeys)).to.be.empty;981 expect(properties).to.be.empty;982 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[];777 expect((await nestedToken.getData())!.properties).to.be.empty;983 expect(tokensData).to.be.empty;984 expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0);778 expect(await targetToken.getProperties()).to.be.empty;985 });986 });779 });987});780});988781989describe('Negative Integration Test: Token Properties', () => {782describe('Negative Integration Test: Token Properties', () => {990 let collection: number;783 let alice: IKeyringPair; // collection owner991 let token: number;784 let bob: IKeyringPair; // collection admin992 let originalSpace: number;785 let charlie: IKeyringPair; // token owner786993 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];787 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];994788995 before(async () => {789 before(async () => {996 await usingApi(async (api, privateKeyWrapper) => {790 await usingPlaygrounds(async (helper, privateKey) => {997 alice = privateKeyWrapper('//Alice');791 const donor = privateKey('//Alice');998 bob = privateKeyWrapper('//Bob');792 let dave: IKeyringPair;999 charlie = privateKeyWrapper('//Charlie');793 [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);1000 const dave = privateKeyWrapper('//Dave');1001794795 // todo:playgrounds probably separate these tests later1002 constitution = [796 constitution = [1003 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},797 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},1004 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},798 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},1010 });804 });1011 });805 });10128061013 async function prepare(mode: CollectionMode, pieces: number) {807 async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise<number> {1014 collection = await createCollectionExpectSuccess({mode: mode});1015 token = await createItemExpectSuccess(alice, collection, mode.type);1016 await addCollectionAdminExpectSuccess(alice, collection, bob.address);1017 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);1018 1019 await usingApi(async api => {1020 let i = 0;808 return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace;1021 for (const passage of constitution) {1022 const signer = passage.signers[0];1023 1024 await expect(executeTransaction(1025 api, 1026 alice, 1027 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 1028 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;1029 1030 await expect(executeTransaction(1031 api, 1032 signer, 1033 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 1034 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;1035 1036 i++;1037 }1038 1039 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;1040 }); 1041 }809 }10428101043 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {811 async function prepare(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint): Promise<number> {1044 await prepare(mode, pieces);812 await token.collection.addAdmin(alice, {Substrate: bob.address});1045 813 await token.transfer(alice, {Substrate: charlie.address}, pieces);1046 await usingApi(async api => {8141047 let i = -1;815 let i = 0;1048 for (const forbiddance of constitution) {816 for (const passage of constitution) {1049 i++;817 i++;1050 if (!forbiddance.permission.mutable) continue;818 const signer = passage.signers[0];1051 819 1052 await expect(executeTransaction(820 await expect(1053 api, 821 token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), 1054 forbiddance.sinner, 1055 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1056 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);822 `on setting permission ${i} by alice`,1057 823 ).to.be.fulfilled;8241058 await expect(executeTransaction(825 await expect(1059 api, 826 token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), 1060 forbiddance.sinner, 1061 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 1062 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);827 `on adding property ${i} by ${signer.address}`,828 ).to.be.fulfilled;1063 }829 }1064 8301065 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();831 const originalSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 1066 expect(properties.consumedSpace).to.be.equal(originalSpace);1067 });832 return originalSpace;1068 }833 }8341069 it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {835 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {836 const originalSpace = await prepare(token, pieces);837838 let i = 0;839 for (const forbiddance of constitution) {840 i++;841 if (!forbiddance.permission.mutable) continue;842843 await expect(844 token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 845 `on failing to change property ${i} by the malefactor`,846 ).to.be.rejectedWith(/common\.NoPermission/);847848 await expect(849 token.deleteProperties(forbiddance.sinner, [`${i}`]), 850 `on failing to delete property ${i} by the malefactor`,851 ).to.be.rejectedWith(/common\.NoPermission/);852 }853854 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 855 expect(consumedSpace).to.be.equal(originalSpace);856 }857858 itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) => {1070 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);859 const collection = await helper.nft.mintCollection(alice);860 const token = await collection.mintToken(alice);861 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);1071 });862 });1072 it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async function() {1073 await requirePallets(this, [Pallets.ReFungible]);10748631075 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);864 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {865 const collection = await helper.rft.mintCollection(alice);866 const token = await collection.mintToken(alice, 100n);867 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);1076 });868 });10778691078 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {870 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {1079 await prepare(mode, pieces);871 const originalSpace = await prepare(token, pieces);8721080 873 let i = 0;1081 await usingApi(async api => {874 for (const permission of constitution) {1082 let i = -1;875 i++;876 if (permission.permission.mutable) continue;8771083 for (const permission of constitution) {878 await expect(879 token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 1084 i++;880 `on failing to change property ${i} by signer #0`,881 ).to.be.rejectedWith(/common\.NoPermission/);8821085 if (permission.permission.mutable) continue;883 await expect(884 token.deleteProperties(permission.signers[0], [i.toString()]), 885 `on failing to delete property ${i} by signer #0`,886 ).to.be.rejectedWith(/common\.NoPermission/);887 }1086 888 1087 await expect(executeTransaction(889 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 1088 api, 1089 permission.signers[0], 1090 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1091 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1092 1093 await expect(executeTransaction(1094 api, 1095 permission.signers[0], 1096 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 1097 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1098 }1099 890 expect(consumedSpace).to.be.equal(originalSpace);1100 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1101 expect(properties.consumedSpace).to.be.equal(originalSpace);1102 }); 1103 }891 }8921104 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {893 itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) => {1105 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);894 const collection = await helper.nft.mintCollection(alice);895 const token = await collection.mintToken(alice);896 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);1106 });897 });1107 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async function() {1108 await requirePallets(this, [Pallets.ReFungible]);11098981110 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);899 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {900 const collection = await helper.rft.mintCollection(alice);901 const token = await collection.mintToken(alice, 100n);902 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);1111 });903 });11129041113 async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {905 async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {1114 await prepare(mode, pieces);906 const originalSpace = await prepare(token, pieces);11159071116 await usingApi(async api => {908 await expect(1117 await expect(executeTransaction(1118 api, 909 token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 1119 alice, 1120 api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 1121 ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);910 'on failing to add a previously non-existent property',911 ).to.be.rejectedWith(/common\.NoPermission/);1122 912 1123 await expect(executeTransaction(913 await expect(1124 api, 914 token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 1125 alice, 1126 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 1127 ), 'on setting a new non-permitted property').to.not.be.rejected;915 'on setting a new non-permitted property',1128 916 ).to.be.fulfilled;9171129 await expect(executeTransaction(918 await expect(1130 api, 919 token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 1131 alice, 1132 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 1133 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);920 'on failing to add a property forbidden by the \'None\' permission',1134 921 ).to.be.rejectedWith(/common\.NoPermission/);9221135 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;923 expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;1136 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();924 925 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 1137 expect(properties.consumedSpace).to.be.equal(originalSpace);926 expect(consumedSpace).to.be.equal(originalSpace);1138 });1139 }927 }9281140 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {929 itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) => {1141 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);930 const collection = await helper.nft.mintCollection(alice);931 const token = await collection.mintToken(alice);932 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);1142 });933 });1143 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async function() {1144 await requirePallets(this, [Pallets.ReFungible]);11459341146 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);935 itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {936 const collection = await helper.rft.mintCollection(alice);937 const token = await collection.mintToken(alice, 100n);938 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);1147 });939 });11489401149 async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {941 async function testForbidsAddingTooManyProperties(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {1150 await prepare(mode, pieces);942 const originalSpace = await prepare(token, pieces);11519431152 await usingApi(async api => {944 await expect(1153 await expect(executeTransaction(1154 api, 945 token.collection.setTokenPropertyPermissions(alice, [1155 alice, 1156 api.tx.unique.setTokenPropertyPermissions(collection, [1157 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 946 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 1158 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},947 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},1159 ]), 948 ]), 949 'on setting new permissions for properties',950 ).to.be.fulfilled;951952 // Mute the general tx parsing error953 {954 console.error = () => {};955 await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))956 .to.be.rejected;957 }958959 await expect(token.setProperties(alice, [1160 ), 'on setting a new non-permitted property').to.not.be.rejected;960 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 961 {key: 'young_years', value: 'neverending'.repeat(1490)},962 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);1161 963 1162 // Mute the general tx parsing error964 expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty;1163 {1164 console.error = () => {};1165 await expect(executeTransaction(1166 api, 1167 alice, 1168 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 1169 )).to.be.rejected;1170 }965 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 1171 1172 await expect(executeTransaction(1173 api, 1174 alice, 1175 api.tx.unique.setTokenProperties(collection, token, [1176 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 1177 {key: 'young_years', value: 'neverending'.repeat(1490)},1178 ]), 1179 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);1180 1181 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;966 expect(consumedSpace).to.be.equal(originalSpace);1182 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1183 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);1184 });1185 }967 }9681186 it('Forbids adding too many properties to a token (NFT)', async () => {969 itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => {1187 await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);970 const collection = await helper.nft.mintCollection(alice);971 const token = await collection.mintToken(alice);972 await testForbidsAddingTooManyProperties(token, 1n);1188 });973 });1189 it('Forbids adding too many properties to a token (ReFungible)', async function() {1190 await requirePallets(this, [Pallets.ReFungible]);11919741192 await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);975 itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {976 const collection = await helper.rft.mintCollection(alice);977 const token = await collection.mintToken(alice, 100n);978 await testForbidsAddingTooManyProperties(token, 100n);1193 });979 });1194});980});11959811196describe('ReFungible token properties permissions tests', () => {982describe('ReFungible token properties permissions tests', () => {1197 let collection: number;983 let alice: IKeyringPair;1198 let token: number;984 let bob: IKeyringPair;985 let charlie: IKeyringPair;11999861200 before(async function() {987 before(async function() {1201 await requirePallets(this, [Pallets.ReFungible]);988 await usingPlaygrounds(async (helper, privateKey) => {989 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);12029901203 await usingApi(async (api, privateKeyWrapper) => {991 const donor = privateKey('//Alice');1204 alice = privateKeyWrapper('//Alice');1205 bob = privateKeyWrapper('//Bob');992 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);1206 charlie = privateKeyWrapper('//Charlie');1207 });993 });1208 });994 });12099951210 beforeEach(async () => {996 async function prepare(helper: UniqueHelper): Promise<UniqueRFTToken> {1211 await usingApi(async api => {997 const collection = await helper.rft.mintCollection(alice);998 const token = await collection.mintToken(alice, 100n);1212 collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});999 1000 await collection.addAdmin(alice, {Substrate: bob.address});1213 token = await createItemExpectSuccess(alice, collection, 'ReFungible');1001 await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);1214 await addCollectionAdminExpectSuccess(alice, collection, bob.address);1002 1003 return token;1004 }121510051216 await expect(executeTransaction(1006 itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1217 api, 1218 alice, 1007 const token = await prepare(helper);10081219 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1009 await token.transfer(alice, {Substrate: charlie.address}, 33n);10101011 await expect(token.setProperties(alice, [1012 {key: 'fractals', value: 'multiverse'}, 1220 )).to.not.be.rejected;1013 ])).to.be.rejectedWith(/common\.NoPermission/);1221 });1222 });1014 });122310151224 it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {1016 itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) => {1225 await usingApi(async api => {1017 const token = await prepare(helper);10181226 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1019 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1227 1020 .to.be.fulfilled;10211228 await expect(executeTransaction(1022 await expect(token.setProperties(alice, [1229 api, 1023 {key: 'fractals', value: 'multiverse'}, 1230 alice, 1024 ])).to.be.fulfilled;10251231 api.tx.unique.setTokenProperties(collection, token, [1026 await token.transfer(alice, {Substrate: charlie.address}, 33n);10271028 await expect(token.setProperties(alice, [1232 {key: 'key', value: 'word'}, 1029 {key: 'fractals', value: 'want to rule the world'}, 1233 ]), 1234 )).to.be.rejectedWith(/common\.NoPermission/);1030 ])).to.be.rejectedWith(/common\.NoPermission/);1235 });1236 });1031 });123710321238 it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {1033 itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1239 await usingApi(async api => {1034 const token = await prepare(helper);1240 await expect(executeTransaction(1241 api, 1242 alice, 1243 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1244 )).to.not.be.rejected;1245 1246 await expect(executeTransaction(1247 api, 1248 alice, 1249 api.tx.unique.setTokenProperties(collection, token, [1250 {key: 'key', value: 'word'}, 1251 ]), 1252 )).to.be.not.rejected;125310351254 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1036 await expect(token.setProperties(alice, [1255 1037 {key: 'fractals', value: 'one headline - why believe it'}, 1256 await expect(executeTransaction(1257 api, 1258 alice, 1259 api.tx.unique.setTokenProperties(collection, token, [1038 ])).to.be.fulfilled;10391260 {key: 'key', value: 'bad word'}, 1040 await token.transfer(alice, {Substrate: charlie.address}, 33n);10411261 ]), 1042 await expect(token.deleteProperties(alice, ['fractals'])).1262 )).to.be.rejectedWith(/common\.NoPermission/);1043 to.be.rejectedWith(/common\.NoPermission/);1263 });1264 });1044 });126510451266 it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {1046 itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) => {1267 await usingApi(async api => {1047 const token = await prepare(helper);1268 await expect(executeTransaction(1269 api, 1270 alice, 1271 api.tx.unique.setTokenProperties(collection, token, [1272 {key: 'key', value: 'word'}, 1273 ]), 1274 )).to.be.not.rejected;127510481276 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1049 await token.transfer(alice, {Substrate: charlie.address}, 33n);1277 10501278 await expect(executeTransaction(1051 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1279 api, 1280 alice, 1052 .to.be.fulfilled;10531281 api.tx.unique.deleteTokenProperties(collection, token, [1054 await expect(token.setProperties(alice, [1282 'key',1055 {key: 'fractals', value: 'multiverse'}, 1283 ]), 1284 )).to.be.rejectedWith(/common\.NoPermission/);1056 ])).to.be.fulfilled;1285 });1286 });1057 });1287});1058});12881059tests/src/nesting/rules-smoke.test.tsdiffbeforeafterbothno changes
tests/src/nesting/unnest.test.tsdiffbeforeafterboth1import {expect} from 'chai';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.162import {tokenIdToAddress} from '../eth/util/helpers';17import {IKeyringPair} from '@polkadot/types/types';3import usingApi, {executeTransaction} from '../substrate/substrate-api';4import {18import {expect, itSub, Pallets, usingPlaygrounds} from '../util/playgrounds';5 createCollectionExpectSuccess,6 createItemExpectSuccess,7 getBalance,8 getTokenOwner,9 normalizeAccountId,10 setCollectionPermissionsExpectSuccess,11 transferExpectSuccess,12 transferFromExpectSuccess,13 requirePallets,14 Pallets,15} from '../util/helpers';16import {IKeyringPair} from '@polkadot/types/types';1718let alice: IKeyringPair;19let bob: IKeyringPair;201921describe('Integration Test: Unnesting', () => {20describe('Integration Test: Unnesting', () => {21 let alice: IKeyringPair;2222 before(async () => {23 before(async () => {23 await usingApi(async (api, privateKeyWrapper) => {24 await usingPlaygrounds(async (helper, privateKey) => {24 alice = privateKeyWrapper('//Alice');25 const donor = privateKey('//Alice');25 bob = privateKeyWrapper('//Bob');26 [alice] = await helper.arrange.createAccounts([50n], donor);26 });27 });27 });28 });282929 it('NFT: allows the owner to successfully unnest a token', async () => {30 itSub('NFT: allows the owner to successfully unnest a token', async ({helper}) => {30 await usingApi(async api => {31 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});31 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});32 const targetToken = await collection.mintToken(alice);32 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});33 33 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');34 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};3536 // Create a nested token34 // Create a nested token37 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);35 const nestedToken = await collection.mintToken(alice, targetToken.nestingAddress());383639 // Unnest37 // Unnest40 await expect(executeTransaction(38 await expect(nestedToken.transferFrom(alice, targetToken.nestingAddress(), {Substrate: alice.address}), 'while unnesting').to.be.fulfilled;41 api,42 alice,43 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),44 ), 'while unnesting').to.not.be.rejected;45 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});39 expect(await nestedToken.getOwner()).to.be.deep.equal({Substrate: alice.address});464047 // Nest and burn41 // Nest and burn48 await transferExpectSuccess(collection, nestedToken, alice, targetAddress);42 await nestedToken.nest(alice, targetToken);49 await expect(executeTransaction(43 await expect(nestedToken.burnFrom(alice, targetToken.nestingAddress()), 'while burning').to.be.fulfilled;50 api,51 alice,52 api.tx.unique.burnFrom(collection, normalizeAccountId(targetAddress), nestedToken, 1),53 ), 'while burning').to.not.be.rejected;54 await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected;44 await expect(nestedToken.getOwner()).to.be.rejected;55 });56 });45 });574658 it('Fungible: allows the owner to successfully unnest a token', async () => {47 itSub('Fungible: allows the owner to successfully unnest a token', async ({helper}) => {59 await usingApi(async api => {48 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});49 const targetToken = await collection.mintToken(alice);5051 const collectionFT = await helper.ft.mintCollection(alice);52 53 // Nest and unnest60 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});54 await collectionFT.mint(alice, 10n, targetToken.nestingAddress());61 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});62 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');55 await expect(collectionFT.transferFrom(alice, targetToken.nestingAddress(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;63 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};6465 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});56 expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(9n);66 const nestedToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');6768 // Nest and unnest69 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');57 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(1n);70 await transferFromExpectSuccess(collectionFT, nestedToken, alice, targetAddress, alice, 1, 'Fungible');715872 // Nest and burn59 // Nest and burn73 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');60 await collectionFT.transfer(alice, targetToken.nestingAddress(), 5n);74 const balanceBefore = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);61 await expect(collectionFT.burnTokensFrom(alice, targetToken.nestingAddress(), 6n), 'while burning').to.be.fulfilled;75 await expect(executeTransaction(62 expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(4n);76 api,77 alice,78 api.tx.unique.burnFrom(collectionFT, normalizeAccountId(targetAddress), nestedToken, 1),79 ), 'while burning').to.not.be.rejected;80 const balanceAfter = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);63 expect(await collectionFT.getBalance(targetToken.nestingAddress())).to.be.equal(0n);81 expect(balanceAfter + BigInt(1)).to.be.equal(balanceBefore);64 expect(await targetToken.getChildren()).to.be.length(0);82 });83 });65 });846685 it('ReFungible: allows the owner to successfully unnest a token', async function() {67 itSub.ifWithPallets('ReFungible: allows the owner to successfully unnest a token', [Pallets.ReFungible], async ({helper}) => {86 await requirePallets(this, [Pallets.ReFungible]);8788 await usingApi(async api => {89 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});90 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});68 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});91 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');69 const targetToken = await collection.mintToken(alice);92 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};937094 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});71 const collectionRFT = await helper.rft.mintCollection(alice);72 73 // Nest and unnest95 const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');74 const token = await collectionRFT.mintToken(alice, 10n, targetToken.nestingAddress());9697 // Nest and unnest98 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');75 await expect(token.transferFrom(alice, targetToken.nestingAddress(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;99 await transferFromExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, alice, 1, 'ReFungible');76 expect(await token.getBalance({Substrate: alice.address})).to.be.equal(9n);77 expect(await token.getBalance(targetToken.nestingAddress())).to.be.equal(1n);10078101 // Nest and burn79 // Nest and burn102 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');80 await token.transfer(alice, targetToken.nestingAddress(), 5n);103 await expect(executeTransaction(81 await expect(token.burnFrom(alice, targetToken.nestingAddress(), 6n), 'while burning').to.be.fulfilled;104 api,105 alice,106 api.tx.unique.burnFrom(collectionRFT, normalizeAccountId(targetAddress), nestedToken, 1),107 ), 'while burning').to.not.be.rejected;108 const balance = await getBalance(api, collectionRFT, normalizeAccountId(targetAddress), nestedToken);82 expect(await token.getBalance({Substrate: alice.address})).to.be.equal(4n);109 expect(balance).to.be.equal(0n);83 expect(await token.getBalance(targetToken.nestingAddress())).to.be.equal(0n);84 expect(await targetToken.getChildren()).to.be.length(0);110 });85 });111 });112});86});11387114describe('Negative Test: Unnesting', () => {88describe('Negative Test: Unnesting', () => {89 let alice: IKeyringPair;90 let bob: IKeyringPair;91115 before(async () => {92 before(async () => {116 await usingApi(async (api, privateKeyWrapper) => {93 await usingPlaygrounds(async (helper, privateKey) => {117 alice = privateKeyWrapper('//Alice');94 const donor = privateKey('//Alice');118 bob = privateKeyWrapper('//Bob');95 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);119 });96 });120 });97 });12198122 it('Disallows a non-owner to unnest/burn a token', async () => {99 itSub('Disallows a non-owner to unnest/burn a token', async ({helper}) => {123 await usingApi(async api => {100 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});124 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});101 const targetToken = await collection.mintToken(alice);125 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});126 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');127 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};128102129 // Create a nested token103 // Create a nested token130 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);104 const nestedToken = await collection.mintToken(alice, targetToken.nestingAddress());131105132 // Try to unnest106 // Try to unnest133 await expect(executeTransaction(107 await expect(nestedToken.unnest(bob, targetToken, {Substrate: alice.address})).to.be.rejectedWith(/common\.ApprovedValueTooLow/);134 api,135 bob,136 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1),137 ), 'while unnesting').to.be.rejectedWith(/^common\.ApprovedValueTooLow$/);138 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});108 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());139109140 // Try to burn110 // Try to burn141 await expect(executeTransaction(111 await expect(nestedToken.burnFrom(bob, targetToken.nestingAddress())).to.be.rejectedWith(/common\.ApprovedValueTooLow/);142 api,143 bob,144 api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),145 ), 'while burning').to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);146 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});112 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAddress());147 });148 });113 });149114150 // todo another test for creating excessive depth matryoshka with Ethereum?115 // todo another test for creating excessive depth matryoshka with Ethereum?151116152 // Recursive nesting117 // Recursive nesting153 it('Prevents Ouroboros creation', async () => {118 itSub('Prevents Ouroboros creation', async ({helper}) => {154 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});119 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});155 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});156 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');120 const targetToken = await collection.mintToken(alice);157121158 // Create a nested token ouroboros122 // Fail to create a nested token ouroboros159 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});123 const nestedToken = await collection.mintToken(alice, targetToken.nestingAddress());160 await expect(transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)})).to.be.rejectedWith(/^structure\.OuroborosDetected$/);124 await expect(targetToken.nest(alice, nestedToken)).to.be.rejectedWith(/^structure\.OuroborosDetected$/);161 });125 });162});126});163127tests/src/util/playgrounds/types.tsdiffbeforeafterboth949495export interface IProperty {95export interface IProperty {96 key: string;96 key: string;97 value: string;97 value?: string;98}98}9999100export interface ITokenPropertyPermission {100export interface ITokenPropertyPermission {101 key: string;101 key: string;102 permission: {102 permission: {103 mutable: boolean;103 mutable?: boolean;104 tokenOwner: boolean;104 tokenOwner?: boolean;105 collectionAdmin: boolean;105 collectionAdmin?: boolean;106 }106 }107}107}108108109export interface IToken {109export interface IToken {110 collectionId: number;110 collectionId: number;111 tokenId: number;111 tokenId: number;112 //nestingAddress: () => {Ethereum: string};112}113}113114114export interface IBlock {115export interface IBlock {tests/src/util/playgrounds/unique.tsdiffbeforeafterboth55 RPC: 'rpc',55 RPC: 'rpc',56 };56 };575758 static getNestingTokenAddress(collectionId: number, tokenId: number) {58 static getNestingTokenAddress(token: IToken) {59 return nesting.tokenIdToAddress(collectionId, tokenId);59 return {Ethereum: this.getNestingTokenAddressRaw(token).toLowerCase()};60 }60 }616162 static getNestingTokenAddressRaw(token: IToken) {63 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);64 }6562 static getDefaultLogger(): ILogger {66 static getDefaultLogger(): ILogger {63 return {67 return {64 log(msg: any, level = 'INFO') {68 log(msg: any, level = 'INFO') {897 }901 }898902899 /**903 /**904 * Get collection properties.905 * 906 * @param collectionId ID of collection907 * @param propertyKeys optionally filter the returned properties to only these keys908 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);909 * @returns array of key-value pairs910 */911 async getProperties(collectionId: number, propertyKeys: string[] | null = null): Promise<IProperty[]> {912 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();913 }914915 /**900 * Deletes onchain properties from the collection.916 * Deletes onchain properties from the collection.901 *917 *902 * @param signer keyring of signer918 * @param signer keyring of signer988 *1004 *989 * @param signer keyring of signer1005 * @param signer keyring of signer990 * @param collectionId ID of collection1006 * @param collectionId ID of collection1007 * @param tokenId ID of token991 * @param fromAddressObj address on behalf of which the token will be burnt1008 * @param fromAddressObj address on behalf of which the token will be burnt992 * @param tokenId ID of token993 * @param amount amount of tokens to be burned. For NFT must be set to 1n1009 * @param amount amount of tokens to be burned. For NFT must be set to 1n994 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1010 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})995 * @returns ```true``` if extrinsic success, otherwise ```false```1011 * @returns ```true``` if extrinsic success, otherwise ```false```996 */1012 */997 async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, amount=1n): Promise<boolean> {1013 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {998 const burnResult = await this.helper.executeExtrinsic(1014 const burnResult = await this.helper.executeExtrinsic(999 signer,1015 signer,1000 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1016 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1117 *1133 *1118 * @param signer keyring of signer1134 * @param signer keyring of signer1119 * @param collectionId ID of collection1135 * @param collectionId ID of collection1120 * @param permissions permissions to change a property by the collection owner or admin1136 * @param permissions permissions to change a property by the collection admin or token owner1121 * @example setTokenPropertyPermissions(1137 * @example setTokenPropertyPermissions(1122 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1138 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1123 * )1139 * )1134 }1150 }113511511136 /**1152 /**1153 * Get token property permissions.1154 * 1155 * @param collectionId ID of collection1156 * @param propertyKeys optionally filter the returned property permissions to only these keys1157 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1158 * @returns array of key-permission pairs1159 */1160 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1161 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1162 }11631164 /**1137 * Set token properties1165 * Set token properties1138 *1166 *1139 * @param signer keyring of signer1167 * @param signer keyring of signer1154 }1182 }115511831156 /**1184 /**1185 * Get properties, metadata assigned to a token.1186 * 1187 * @param collectionId ID of collection1188 * @param tokenId ID of token1189 * @param propertyKeys optionally filter the returned properties to only these keys1190 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1191 * @returns array of key-value pairs1192 */1193 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys: string[] | null = null): Promise<IProperty[]> {1194 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1195 }11961197 /**1157 * Delete the provided properties of a token1198 * Delete the provided properties of a token1158 * @param signer keyring of signer1199 * @param signer keyring of signer1159 * @param collectionId ID of collection1200 * @param collectionId ID of collection1339 * @returns ```true``` if extrinsic success, otherwise ```false```1380 * @returns ```true``` if extrinsic success, otherwise ```false```1340 */1381 */1341 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1382 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1342 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1383 const rootTokenAddress = this.helper.util.getNestingTokenAddress(rootTokenObj);1343 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1384 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1344 if(!result) {1385 if(!result) {1345 throw Error('Unable to nest token!');1386 throw Error('Unable to nest token!');1357 * @returns ```true``` if extrinsic success, otherwise ```false```1398 * @returns ```true``` if extrinsic success, otherwise ```false```1358 */1399 */1359 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1400 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1360 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1401 const rootTokenAddress = this.helper.util.getNestingTokenAddress(rootTokenObj);1361 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1402 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1362 if(!result) {1403 if(!result) {1363 throw Error('Unable to unnest token!');1404 throw Error('Unable to unnest token!');1377 * })1418 * })1378 * @returns object of the created collection1419 * @returns object of the created collection1379 */1420 */1380 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise<UniqueNFTCollection> {1421 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1381 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1422 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1382 }1423 }138314241563 * })1604 * })1564 * @returns object of the created collection1605 * @returns object of the created collection1565 */1606 */1566 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise<UniqueRFTCollection> {1607 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1567 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1608 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1568 }1609 }156916101633 * @param tokenId ID of token1674 * @param tokenId ID of token1634 * @param amount number of pieces to be burnt1675 * @param amount number of pieces to be burnt1635 * @example burnToken(aliceKeyring, 10, 5);1676 * @example burnToken(aliceKeyring, 10, 5);1636 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1677 * @returns ```true``` and burnt token number, if extrinsic is successful. Otherwise ```false``` and ```null```1637 */1678 */1638 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<{ success: boolean; token: number | null; }> {1679 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<{ success: boolean; token: number | null; }> {1639 return await super.burnToken(signer, collectionId, tokenId, amount);1680 return await super.burnToken(signer, collectionId, tokenId, amount);1640 }1681 }164116821642 /**1683 /**1684 * Destroys a concrete instance of RFT on behalf of the owner.1685 * @param signer keyring of signer1686 * @param collectionId ID of collection1687 * @param tokenId ID of token1688 * @param fromAddressObj address on behalf of which the token will be burnt1689 * @param amount number of pieces to be burnt1690 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1691 * @returns ```true``` if extrinsic success, otherwise ```false```1692 */1693 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1694 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1695 }16961697 /**1643 * Set, change, or remove approved address to transfer the ownership of the RFT.1698 * Set, change, or remove approved address to transfer the ownership of the RFT.1644 *1699 *1645 * @param signer keyring of signer1700 * @param signer keyring of signer1711 * }, 18)1766 * }, 18)1712 * @returns newly created fungible collection1767 * @returns newly created fungible collection1713 */1768 */1714 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0): Promise<UniqueFTCollection> {1769 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1715 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1770 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1716 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1771 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1717 collectionOptions.mode = {fungible: decimalPoints};1772 collectionOptions.mode = {fungible: decimalPoints};1840 * @returns ```true``` if extrinsic success, otherwise ```false```1895 * @returns ```true``` if extrinsic success, otherwise ```false```1841 */1896 */1842 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1897 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1843 return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, amount);1898 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);1844 }1899 }184519001846 /**1901 /**2161}2216}21622217216322182164class UniqueCollectionBase {2219export class UniqueCollectionBase {2165 helper: UniqueHelper;2220 helper: UniqueHelper;2166 collectionId: number;2221 collectionId: number;216722222194 return await this.helper.collection.getEffectiveLimits(this.collectionId);2249 return await this.helper.collection.getEffectiveLimits(this.collectionId);2195 }2250 }219622512252 async getProperties(propertyKeys: string[] | null = null) {2253 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);2254 }22552197 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {2256 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {2198 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);2257 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);2199 }2258 }2260}2319}22612320226223212263class UniqueNFTCollection extends UniqueCollectionBase {2322export class UniqueNFTCollection extends UniqueCollectionBase {2264 getTokenObject(tokenId: number) {2323 getTokenObject(tokenId: number) {2265 return new UniqueNFTToken(tokenId, this);2324 return new UniqueNFTToken(tokenId, this);2266 }2325 }2285 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2344 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2286 }2345 }228723462347 async getPropertyPermissions(propertyKeys: string[] | null = null) {2348 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);2349 }23502351 async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) {2352 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);2353 }23542288 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2355 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2289 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2356 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2290 }2357 }2313 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);2380 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);2314 }2381 }231523822383 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {2384 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);2385 }23862316 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2387 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2317 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);2388 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);2318 }2389 }2335}2406}23362407233724082338class UniqueRFTCollection extends UniqueCollectionBase {2409export class UniqueRFTCollection extends UniqueCollectionBase {2339 getTokenObject(tokenId: number) {2410 getTokenObject(tokenId: number) {2340 return new UniqueRFTToken(tokenId, this);2411 return new UniqueRFTToken(tokenId, this);2341 }2412 }234224132414 async getToken(tokenId: number, blockHashAt?: string) {2415 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);2416 }24172343 async getTokensByAddress(addressObj: ICrossAccountId) {2418 async getTokensByAddress(addressObj: ICrossAccountId) {2344 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2419 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2345 }2420 }2356 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2431 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2357 }2432 }235824332434 async getPropertyPermissions(propertyKeys: string[] | null = null) {2435 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);2436 }24372438 async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) {2439 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);2440 }24412359 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {2442 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {2360 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2443 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2361 }2444 }2388 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);2471 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);2389 }2472 }239024732474 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {2475 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);2476 }24772391 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2478 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2392 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);2479 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);2393 }2480 }2402}2489}24032490240424912405class UniqueFTCollection extends UniqueCollectionBase {2492export class UniqueFTCollection extends UniqueCollectionBase {2406 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {2493 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {2407 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);2494 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);2408 }2495 }2449}2536}24502537245125382452class UniqueTokenBase implements IToken {2539export class UniqueTokenBase implements IToken {2453 collection: UniqueNFTCollection | UniqueRFTCollection;2540 collection: UniqueNFTCollection | UniqueRFTCollection;2454 collectionId: number;2541 collectionId: number;2455 tokenId: number;2542 tokenId: number;2464 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2551 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2465 }2552 }246625532554 async getProperties(propertyKeys: string[] | null = null) {2555 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);2556 }25572467 async setProperties(signer: TSigner, properties: IProperty[]) {2558 async setProperties(signer: TSigner, properties: IProperty[]) {2468 return await this.collection.setTokenProperties(signer, this.tokenId, properties);2559 return await this.collection.setTokenProperties(signer, this.tokenId, properties);2469 }2560 }247025612471 async deleteProperties(signer: TSigner, propertyKeys: string[]) {2562 async deleteProperties(signer: TSigner, propertyKeys: string[]) {2472 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);2563 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);2473 }2564 }25652566 nestingAddress() {2567 return this.collection.helper.util.getNestingTokenAddress(this);2568 }2474}2569}24752570247625712477class UniqueNFTToken extends UniqueTokenBase {2572export class UniqueNFTToken extends UniqueTokenBase {2478 collection: UniqueNFTCollection;2573 collection: UniqueNFTCollection;247925742480 constructor(tokenId: number, collection: UniqueNFTCollection) {2575 constructor(tokenId: number, collection: UniqueNFTCollection) {2525 async burn(signer: TSigner) {2620 async burn(signer: TSigner) {2526 return await this.collection.burnToken(signer, this.tokenId);2621 return await this.collection.burnToken(signer, this.tokenId);2527 }2622 }26232624 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {2625 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);2626 }2528}2627}252926282530class UniqueRFTToken extends UniqueTokenBase {2629export class UniqueRFTToken extends UniqueTokenBase {2531 collection: UniqueRFTCollection;2630 collection: UniqueRFTCollection;253226312533 constructor(tokenId: number, collection: UniqueRFTCollection) {2632 constructor(tokenId: number, collection: UniqueRFTCollection) {2534 super(tokenId, collection);2633 super(tokenId, collection);2535 this.collection = collection;2634 this.collection = collection;2536 }2635 }253726362637 async getData(blockHashAt?: string) {2638 return await this.collection.getToken(this.tokenId, blockHashAt);2639 }26402538 async getTop10Owners() {2641 async getTop10Owners() {2539 return await this.collection.getTop10TokenOwners(this.tokenId);2642 return await this.collection.getTop10TokenOwners(this.tokenId);2540 }2643 }256926722570 async burn(signer: TSigner, amount=1n) {2673 async burn(signer: TSigner, amount=1n) {2571 return await this.collection.burnToken(signer, this.tokenId, amount);2674 return await this.collection.burnToken(signer, this.tokenId, amount);2675 }26762677 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {2678 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);2572 }2679 }2573}2680}25742681