difftreelog
Merge pull request #607 from UniqueNetwork/tests/nesting
in: master
11 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.getTokenAddress({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, UniqueNFToken} 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<UniqueNFToken[]> {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].nestingAccount(), tokens[7].nestingAccount()),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.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());52 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});53 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());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.nestingAccount());77 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});68 expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccountInLowerCase());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.nestingAccount(), tokenB.nestingAccount())).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.nestingAccountInLowerCase());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.nestingAccount());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.nestingAccount(), 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.nestingAccount(), tokenA.nestingAccount(), 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.nestingAccount(), tokenA.nestingAccount(), 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.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());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.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());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.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());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.nestingAccount());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.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());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.nestingAccount());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.nestingAccount());232261 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});233 // Create an immediately nested token234 const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccountInLowerCase());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.nestingAccount());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.nestingAccount());284258285 // Nest a new token259 // Create an immediately nested token286 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');260 await collectionFT.mint(charlie, 5n, targetToken.nestingAccount());261 expect(await collectionFT.getBalance(targetToken.nestingAccount())).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.nestingAccount(), 2n);288 });266 expect(await collectionFT.getBalance(targetToken.nestingAccount())).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.nestingAccount());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.nestingAccount());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.nestingAccount());303 collectionFT,284 expect(await collectionFT.getBalance(targetToken.nestingAccount())).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.nestingAccount(), 2n);311 });289 expect(await collectionFT.getBalance(targetToken.nestingAccount())).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.nestingAccount());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.nestingAccount());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.nestingAccount());329 collectionRFT,308 expect(await nestedToken.getBalance(targetToken.nestingAccount())).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.nestingAccount(), 2n);337 });313 expect(await newToken.getBalance(targetToken.nestingAccount())).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.nestingAccount());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.nestingAccount());349328350 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});329 // Create an immediately nested token330 const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount());331 expect(await nestedToken.getBalance(targetToken.nestingAccount())).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.nestingAccount(), 2n);362 });336 expect(await newToken.getBalance(targetToken.nestingAccount())).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.nestingAccount());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.nestingAccount()))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.nestingAccount()))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.nestingAccount());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.nestingAccount()))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.nestingAccount());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.nestingAccount());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.nestingAccountInLowerCase());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.nestingAccount()))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.nestingAccount()))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.nestingAccount());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.nestingAccount());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.nestingAccount());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.nestingAccount());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.nestingAccount()))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.nestingAccount()))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.nestingAccount(), 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.nestingAccount());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.nestingAccount(), {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.nestingAccount())).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.nestingAccount());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.nestingAccount(), {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.nestingAccount())).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.nestingAccount()))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.nestingAccount(), 1n))578 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);708579709 // Try to create a nested token in the wrong collection580 expect(await collectionFT.getBalance(targetToken.nestingAccount())).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.nestingAccount()))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.nestingAccount(), 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.nestingAccount());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.nestingAccount())).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.nestingAccount());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.nestingAccount(), {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.nestingAccount())).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.nestingAccount());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.nestingAccount())).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.nestingAccount());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.nestingAccount(), {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.nestingAccount())).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.nestingAccount()))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.nestingAccount(), 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';2import usingApi, {executeTransaction} from '../substrate/substrate-api';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.3import {4 addCollectionAdminExpectSuccess,5 CollectionMode,6 createCollectionExpectSuccess,7 setCollectionPermissionsExpectSuccess,8 createItemExpectSuccess,9 getCreateCollectionResult,10 transferExpectSuccess,11 requirePallets,12 Pallets,13} from '../util/helpers';2// This file is part of Unique Network.14import {IKeyringPair} from '@polkadot/types/types';15import {tokenIdToAddress} from '../eth/util/helpers';16317let alice: IKeyringPair;4// Unique Network is free software: you can redistribute it and/or modify18let bob: IKeyringPair;5// it under the terms of the GNU General Public License as published by19let charlie: IKeyringPair;6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.20821describe('Composite Properties Test', () => {9// Unique Network is distributed in the hope that it will be useful,22 before(async () => {23 await usingApi(async (api, privateKeyWrapper) => {10// but WITHOUT ANY WARRANTY; without even the implied warranty of24 alice = privateKeyWrapper('//Alice');11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the25 bob = privateKeyWrapper('//Bob');12// GNU General Public License for more details.26 });27 });281329 async function testMakeSureSuppliesRequired(mode: CollectionMode) {14// You should have received a copy of the GNU General Public License30 await usingApi(async api => {31 const collectionId = await createCollectionExpectSuccess({mode: mode});15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.321633 const collectionOption = await api.rpc.unique.collectionById(collectionId);17import {IKeyringPair} from '@polkadot/types/types';34 expect(collectionOption.isSome).to.be.true;18import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';35 let collection = collectionOption.unwrap();36 expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;19import {UniqueHelper, UniqueBaseCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../util/playgrounds/unique';37 expect(collection.properties.toHuman()).to.be.empty;382039 const propertyPermissions = [21// ---------- COLLECTION PROPERTIES40 {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;482249 const collectionProperties = [50 {key: 'black_hole', value: 'LIGO'},23describe('Integration Test: Collection Properties', () => {51 {key: 'electron', value: 'come bond'}, 52 ];53 await expect(executeTransaction(54 api, 55 alice, 24 let alice: IKeyringPair;56 api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 25 let bob: IKeyringPair;57 )).to.not.be.rejected;582659 collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();27 before(async () => {28 await usingPlaygrounds(async (helper, privateKey) => {60 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);29 const donor = privateKey('//Alice');61 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);30 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);62 });31 });63 }32 });643365 it('Makes sure collectionById supplies required fields for NFT', async () => {34 itSub('Properties are initially empty', async ({helper}) => {66 await testMakeSureSuppliesRequired({type: 'NFT'});35 const collection = await helper.nft.mintCollection(alice);36 expect(await collection.getProperties()).to.be.empty;67 });37 });683869 it('Makes sure collectionById supplies required fields for ReFungible', async function() {39 async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {40 // As owner70 await requirePallets(this, [Pallets.ReFungible]);41 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;714272 await testMakeSureSuppliesRequired({type: 'ReFungible'});43 await collection.addAdmin(alice, {Substrate: bob.address});73 });74});754476// ---------- COLLECTION PROPERTIES45 // As administrator46 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;774778describe('Integration Test: Collection Properties', () => {48 const properties = await collection.getProperties();79 before(async () => {49 expect(properties).to.include.deep.members([80 await usingApi(async (api, privateKeyWrapper) => {50 {key: 'electron', value: 'come bond'},81 alice = privateKeyWrapper('//Alice');51 {key: 'black_hole', value: ''},52 ]);82 bob = privateKeyWrapper('//Bob');53 }5455 itSub('Sets properties for a NFT collection', async ({helper}) => {83 });56 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));84 });57 });855886 it('Reads properties from a collection', async () => {59 itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {87 await usingApi(async api => {60 await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));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 });61 });946263 async function testCheckValidNames(collection: UniqueBaseCollection) {64 // alpha symbols65 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;956696 async function testSetsPropertiesForCollection(mode: string) {67 // numeric symbols97 await usingApi(async api => {68 await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;98 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));99 const {collectionId} = getCreateCollectionResult(events);10069101 // As owner70 // underscore symbol102 await expect(executeTransaction(71 await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;103 api, 104 bob, 105 api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 106 )).to.not.be.rejected;10772108 await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);73 // dash symbol74 await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;10975110 // As administrator76 // dot symbol111 await expect(executeTransaction(77 await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;112 api, 113 alice, 114 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 115 )).to.not.be.rejected;11678117 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();79 const properties = await collection.getProperties();118 expect(properties).to.be.deep.equal([80 expect(properties).to.include.deep.members([119 {key: 'electron', value: 'come bond'},81 {key: 'answer', value: ''},82 {key: '451', value: ''},120 {key: 'black_hole', value: ''},83 {key: 'black_hole', value: ''},84 {key: '-', value: ''},121 ]);85 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},122 });86 ]);123 }87 }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]);12988130 await testSetsPropertiesForCollection('ReFungible');89 itSub('Check valid names for NFT collection properties keys', async ({helper}) => {90 await testCheckValidNames(await helper.nft.mintCollection(alice));131 });91 });13292133 async function testCheckValidNames(mode: string) {93 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();94 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 });95 });187 it('Check valid names for ReFungible collection properties keys', async function() {188 await requirePallets(this, [Pallets.ReFungible]);18996190 await testCheckValidNames('ReFungible');97 async function testChangesProperties(collection: UniqueBaseCollection) {191 });98 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;19299193 async function testChangesProperties(mode: CollectionMode) {100 // 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(101 await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;205 api, 102206 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();103 const properties = await collection.getProperties();211 expect(properties).to.be.deep.equal([104 expect(properties).to.include.deep.members([212 {key: 'electron', value: 'bonded'},105 {key: 'electron', value: 'come bond'},213 {key: 'black_hole', value: 'LIGO'},106 {key: 'black_hole', value: 'LIGO'},214 ]);107 ]);215 });216 }108 }109217 it('Changes properties of a NFT collection', async () => {110 itSub('Changes properties of a NFT collection', async ({helper}) => {218 await testChangesProperties({type: 'NFT'});111 await testChangesProperties(await helper.nft.mintCollection(alice));219 });112 });220 it('Changes properties of a ReFungible collection', async function() {221 await requirePallets(this, [Pallets.ReFungible]);222113223 await testChangesProperties({type: 'ReFungible'});114 itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {115 await testChangesProperties(await helper.rft.mintCollection(alice));224 });116 });225117226 async function testDeleteProperties(mode: CollectionMode) {118 async function testDeleteProperties(collection: UniqueBaseCollection) {227 await usingApi(async api => {119 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});120229 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(121 await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;237 api, 122238 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();123 const properties = await collection.getProperties(['black_hole', 'electron']);243 expect(properties).to.be.deep.equal([124 expect(properties).to.be.deep.equal([244 {key: 'black_hole', value: 'LIGO'},125 {key: 'black_hole', value: 'LIGO'},245 ]);126 ]);246 }); 247 }127 }128248 it('Deletes properties of a NFT collection', async () => {129 itSub('Deletes properties of a NFT collection', async ({helper}) => {249 await testDeleteProperties({type: 'NFT'});130 await testDeleteProperties(await helper.nft.mintCollection(alice));250 });131 });251 it('Deletes properties of a ReFungible collection', async function() {252 await requirePallets(this, [Pallets.ReFungible]);253132254 await testDeleteProperties({type: 'ReFungible'});133 itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {134 await testDeleteProperties(await helper.rft.mintCollection(alice));255 });135 });256});136});257137258describe('Negative Integration Test: Collection Properties', () => {138describe('Negative Integration Test: Collection Properties', () => {139 let alice: IKeyringPair;140 let bob: IKeyringPair;141259 before(async () => {142 before(async () => {260 await usingApi(async (api, privateKeyWrapper) => {143 await usingPlaygrounds(async (helper, privateKey) => {261 alice = privateKeyWrapper('//Alice');144 const donor = privateKey('//Alice');262 bob = privateKeyWrapper('//Bob');145 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);263 });146 });264 });147 });265 148 266 async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {149 async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueBaseCollection) { 267 await usingApi(async api => {268 const collection = await createCollectionExpectSuccess({mode: mode});150 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/);151 .to.be.rejectedWith(/common\.NoPermission/);275 152276 const properties = (await api.query.common.collectionProperties(collection)).toJSON();153 expect(await collection.getProperties()).to.be.empty;277 expect(properties.map).to.be.empty;278 expect(properties.consumedSpace).to.equal(0);279 }); 280 }154 }155281 it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {156 itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {282 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});157 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));283 });158 });284 it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async function() {285 await requirePallets(this, [Pallets.ReFungible]);286159287 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});160 itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {161 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));288 });162 });289 163 290 async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {164 async function testFailsSetPropertiesThatExeedLimits(collection: UniqueBaseCollection) {291 await usingApi(async api => {165 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 166 295 // Mute the general tx parsing error, too many bytes to process167 // Mute the general tx parsing error, too many bytes to process296 {168 {297 console.error = () => {};169 console.error = () => {};298 await expect(executeTransaction(170 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))}]), 171 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},302 )).to.be.rejected;172 ])).to.be.rejected;303 }173 }304 174305 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();175 expect(await collection.getProperties(['electron'])).to.be.empty;306 expect(properties).to.be.empty;176307 308 await expect(executeTransaction(177 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))}, 178 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 313 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 179 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 314 ]), 180 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);315 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);181316 317 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();182 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;318 expect(properties).to.be.empty;319 }); 320 }183 }184321 it('Fails to set properties that exceed the limits (NFT)', async () => {185 itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => {322 await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});186 await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));323 });187 });324 it('Fails to set properties that exceed the limits (ReFungible)', async function() {325 await requirePallets(this, [Pallets.ReFungible]);326188327 await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});189 itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {190 await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));328 });191 });329 192 330 async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {193 async function testFailsSetMorePropertiesThanAllowed(collection: UniqueBaseCollection) {331 await usingApi(async api => {194 const propertiesToBeSet = [];332 const collection = await createCollectionExpectSuccess({mode: mode});333 334 const propertiesToBeSet = [];335 for (let i = 0; i < 65; i++) {195 for (let i = 0; i < 65; i++) {336 propertiesToBeSet.push({196 propertiesToBeSet.push({337 key: 'electron_' + i,197 key: 'electron_' + i,338 value: Math.random() > 0.5 ? 'high' : 'low',198 value: Math.random() > 0.5 ? 'high' : 'low',339 });199 });340 }200 }341 201342 await expect(executeTransaction(202 await expect(collection.setProperties(alice, propertiesToBeSet)).343 api, 344 alice, 345 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 346 )).to.be.rejectedWith(/common\.PropertyLimitReached/);203 to.be.rejectedWith(/common\.PropertyLimitReached/);347 204348 const properties = (await api.query.common.collectionProperties(collection)).toJSON();205 expect(await collection.getProperties()).to.be.empty;349 expect(properties.map).to.be.empty;350 expect(properties.consumedSpace).to.equal(0);351 }); 352 }206 }207353 it('Fails to set more properties than it is allowed (NFT)', async () => {208 itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {354 await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});209 await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));355 });210 });356 it('Fails to set more properties than it is allowed (ReFungible)', async function() {357 await requirePallets(this, [Pallets.ReFungible]);358211359 await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});212 itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {213 await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));360 });214 });361 215 362 async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {216 async function testFailsSetPropertiesWithInvalidNames(collection: UniqueBaseCollection) {363 await usingApi(async api => {217 const invalidProperties = [364 const collection = await createCollectionExpectSuccess({mode: mode});365 366 const invalidProperties = [367 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],218 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],368 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],219 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],369 [{key: 'déjà vu', value: 'hmm...'}],220 [{key: 'déjà vu', value: 'hmm...'}],370 ];221 ];371 222372 for (let i = 0; i < invalidProperties.length; i++) {223 for (let i = 0; i < invalidProperties.length; i++) {373 await expect(executeTransaction(224 await expect(374 api, 225 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/);226 `on rejecting the new badly-named property #${i}`,227 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);378 }228 }379 229380 await expect(executeTransaction(230 await expect(381 api, 231 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/);232 'on rejecting an unnamed property',385 233 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);234386 await expect(executeTransaction(235 await expect(387 api, 236 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;237 'on setting the correctly-but-still-badly-named property',393 238 ).to.be.fulfilled;239394 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');240 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');395 241396 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();242 const properties = await collection.getProperties(keys);397 expect(properties).to.be.deep.equal([243 expect(properties).to.be.deep.equal([398 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},244 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},399 ]);245 ]);400 246401 for (let i = 0; i < invalidProperties.length; i++) {247 for (let i = 0; i < invalidProperties.length; i++) {402 await expect(executeTransaction(248 await expect(403 api, 249 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/);250 `on trying to delete the non-existent badly-named property #${i}`,251 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);407 }252 }408 });409 }253 }254410 it('Fails to set properties with invalid names (NFT)', async () => {255 itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => {411 await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});256 await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));412 });257 });413 it('Fails to set properties with invalid names (ReFungible)', async function() {414 await requirePallets(this, [Pallets.ReFungible]);415258416 await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});259 itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {260 await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));417 });261 });418});262});419263420// ---------- ACCESS RIGHTS264// ---------- ACCESS RIGHTS421265422describe('Integration Test: Access Rights to Token Properties', () => {266describe('Integration Test: Access Rights to Token Properties', () => {267 let alice: IKeyringPair;268 let bob: IKeyringPair;269423 before(async () => {270 before(async () => {424 await usingApi(async (api, privateKeyWrapper) => {271 await usingPlaygrounds(async (helper, privateKey) => {425 alice = privateKeyWrapper('//Alice');272 const donor = privateKey('//Alice');426 bob = privateKeyWrapper('//Bob');273 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);427 });274 });428 });275 });429 276 430 it('Reads access rights to properties of a collection', async () => {277 itSub('Reads access rights to properties of a collection', async ({helper}) => {431 await usingApi(async api => {432 const collection = await createCollectionExpectSuccess();278 const collection = await helper.nft.mintCollection(alice);433 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();279 const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON();434 expect(propertyRights).to.be.empty;280 expect(propertyRights).to.be.empty;435 });436 });281 });437 282 438 async function testSetsAccessRightsToProperties(mode: CollectionMode) {283 async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 439 await usingApi(async api => {440 const collection = await createCollectionExpectSuccess({mode: mode});441 284 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;285 .to.be.fulfilled;447 286448 await addCollectionAdminExpectSuccess(alice, collection, bob.address);287 await collection.addAdmin(alice, {Substrate: bob.address});449 288450 await expect(executeTransaction(289 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;290 .to.be.fulfilled;455 291456 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();292 const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);457 expect(propertyRights).to.be.deep.equal([293 expect(propertyRights).to.include.deep.members([458 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},294 {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},459 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},295 {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},460 ]);296 ]);461 }); 462 }297 }298463 it('Sets access rights to properties of a collection (NFT)', async () => {299 itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => {464 await testSetsAccessRightsToProperties({type: 'NFT'});300 await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));465 });301 });466 it('Sets access rights to properties of a collection (ReFungible)', async function() {467 await requirePallets(this, [Pallets.ReFungible]);468302469 await testSetsAccessRightsToProperties({type: 'ReFungible'});303 itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {304 await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));470 });305 });471 306 472 async function testChangesAccessRightsToProperty(mode: CollectionMode) {307 async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) {473 await usingApi(async api => {474 const collection = await createCollectionExpectSuccess({mode: mode});308 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;309 .to.be.fulfilled;481 310482 await expect(executeTransaction(311 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;312 .to.be.fulfilled;487 313488 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();314 const propertyRights = await collection.getPropertyPermissions();489 expect(propertyRights).to.be.deep.equal([315 expect(propertyRights).to.be.deep.equal([490 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},316 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},491 ]);317 ]);492 });493 }318 }319494 it('Changes access rights to properties of a NFT collection', async () => {320 itSub('Changes access rights to properties of a NFT collection', async ({helper}) => {495 await testChangesAccessRightsToProperty({type: 'NFT'});321 await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));496 });322 });497 it('Changes access rights to properties of a ReFungible collection', async function() {498 await requirePallets(this, [Pallets.ReFungible]);499323500 await testChangesAccessRightsToProperty({type: 'ReFungible'});324 itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {325 await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));501 });326 });502});327});503328504describe('Negative Integration Test: Access Rights to Token Properties', () => {329describe('Negative Integration Test: Access Rights to Token Properties', () => {330 let alice: IKeyringPair;331 let bob: IKeyringPair;332505 before(async () => {333 before(async () => {506 await usingApi(async (api, privateKeyWrapper) => {334 await usingPlaygrounds(async (helper, privateKey) => {507 alice = privateKeyWrapper('//Alice');335 const donor = privateKey('//Alice');508 bob = privateKeyWrapper('//Bob');336 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);509 });337 });510 });338 });511339512 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {340 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) {513 await usingApi(async api => {514 const collection = await createCollectionExpectSuccess({mode: mode});341 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/);342 .to.be.rejectedWith(/common\.NoPermission/);521 343522 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();344 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);523 expect(propertyRights).to.be.empty;345 expect(propertyRights).to.be.empty;524 });525 }346 }347526 it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {348 itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) => {527 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});349 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));528 });350 });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]);531351532 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});352 itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {353 await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));533 });354 });534355535 async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {356 async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { 536 await usingApi(async api => {537 const collection = await createCollectionExpectSuccess({mode: mode});538 357 const constitution = [];539 const constitution = [];540 for (let i = 0; i < 65; i++) {358 for (let i = 0; i < 65; i++) {541 constitution.push({359 constitution.push({542 key: 'property_' + i,360 key: 'property_' + i,543 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},361 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},544 });362 });545 }363 }546 364547 await expect(executeTransaction(365 await expect(collection.setTokenPropertyPermissions(alice, constitution))548 api, 549 alice, 550 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 551 )).to.be.rejectedWith(/common\.PropertyLimitReached/);366 .to.be.rejectedWith(/common\.PropertyLimitReached/);552 367553 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();368 const propertyRights = await collection.getPropertyPermissions();554 expect(propertyRights).to.be.empty;369 expect(propertyRights).to.be.empty;555 }); 556 }370 }371557 it('Prevents from adding too many possible properties (NFT)', async () => {372 itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => {558 await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});373 await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));559 });374 });560 it('Prevents from adding too many possible properties (ReFungible)', async function() {561 await requirePallets(this, [Pallets.ReFungible]);562375563 await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});376 itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {377 await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));564 });378 });565379566 async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {380 async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) {567 await usingApi(async api => {568 const collection = await createCollectionExpectSuccess({mode: mode});381 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;382 .to.be.fulfilled;575 383576 await expect(executeTransaction(384 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/);385 .to.be.rejectedWith(/common\.NoPermission/);581 386582 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();387 const propertyRights = await collection.getPropertyPermissions(['skullduggery']);583 expect(propertyRights).to.deep.equal([388 expect(propertyRights).to.deep.equal([584 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},389 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},585 ]);390 ]);586 }); 587 }391 }392588 it('Prevents access rights to be modified if constant (NFT)', async () => {393 itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => {589 await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});394 await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));590 });395 });591 it('Prevents access rights to be modified if constant (ReFungible)', async function() {592 await requirePallets(this, [Pallets.ReFungible]);593396594 await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});397 itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {398 await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));595 });399 });596400597 async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {401 async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) {598 await usingApi(async api => {599 const collection = await createCollectionExpectSuccess({mode: mode});402 const invalidProperties = [600 601 const invalidProperties = [602 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],403 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],603 [{key: 'G#4', permission: {tokenOwner: true}}],404 [{key: 'G#4', permission: {tokenOwner: true}}],604 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],405 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],605 ];406 ];606 407607 for (let i = 0; i < invalidProperties.length; i++) {408 for (let i = 0; i < invalidProperties.length; i++) {608 await expect(executeTransaction(409 await expect(609 api, 410 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/);411 `on setting the new badly-named property #${i}`,412 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);613 }413 }614 414615 await expect(executeTransaction(415 await expect(616 api, 416 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/);417 'on rejecting an unnamed property',620 418 ).to.be.rejectedWith(/common\.EmptyPropertyKey/);419621 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string420 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string622 await expect(executeTransaction(421 await expect(623 api, 422 collection.setTokenPropertyPermissions(alice, [624 alice, 625 api.tx.unique.setTokenPropertyPermissions(collection, [626 {key: correctKey, permission: {collectionAdmin: true}},423 {key: correctKey, permission: {collectionAdmin: true}},627 ]), 424 ]), 628 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;425 'on setting the correctly-but-still-badly-named property',629 426 ).to.be.fulfilled;427630 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');428 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');631 429632 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();430 const propertyRights = await collection.getPropertyPermissions(keys);633 expect(propertyRights).to.be.deep.equal([431 expect(propertyRights).to.be.deep.equal([634 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},432 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},635 ]);433 ]);636 });637 }434 }435638 it('Prevents adding properties with invalid names (NFT)', async () => {436 itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => {639 await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});437 await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));640 });438 });641 it('Prevents adding properties with invalid names (ReFungible)', async function() {642 await requirePallets(this, [Pallets.ReFungible]);643439644 await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});440 itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {441 await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));645 });442 });646});443});647444648// ---------- TOKEN PROPERTIES445// ---------- TOKEN PROPERTIES649446650describe('Integration Test: Token Properties', () => {447describe('Integration Test: Token Properties', () => {448 let alice: IKeyringPair; // collection owner449 let bob: IKeyringPair; // collection admin450 let charlie: IKeyringPair; // token owner451651 let permissions: {permission: any, signers: IKeyringPair[]}[];452 let permissions: {permission: any, signers: IKeyringPair[]}[];652453653 before(async () => {454 before(async () => {654 await usingApi(async (api, privateKeyWrapper) => {455 await usingPlaygrounds(async (helper, privateKey) => {655 alice = privateKeyWrapper('//Alice'); // collection owner456 const donor = privateKey('//Alice');656 bob = privateKeyWrapper('//Bob'); // collection admin457 [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 });458 });459460 // todo:playgrounds probably separate these tests later461 permissions = [462 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},463 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},464 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},465 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},466 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},467 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},468 ];668 });469 });669 470 670 async function testReadsYetEmptyProperties(mode: CollectionMode) {471 async function testReadsYetEmptyProperties(token: UniqueNFToken | UniqueRFToken) {671 await usingApi(async api => {672 const collection = await createCollectionExpectSuccess({mode: mode});472 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;473 expect(properties).to.be.empty;677 expect(properties.consumedSpace).to.be.equal(0);474678 475 const tokenData = await token.getData();679 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;680 expect(tokenData).to.be.empty;476 expect(tokenData!.properties).to.be.empty;681 });682 }477 }478683 it('Reads yet empty properties of a token (NFT)', async () => {479 itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {684 await testReadsYetEmptyProperties({type: 'NFT'});480 const collection = await helper.nft.mintCollection(alice);481 const token = await collection.mintToken(alice);482 await testReadsYetEmptyProperties(token);685 });483 });686 it('Reads yet empty properties of a token (ReFungible)', async function() {687 await requirePallets(this, [Pallets.ReFungible]);688484689 await testReadsYetEmptyProperties({type: 'ReFungible'});485 itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {486 const collection = await helper.rft.mintCollection(alice);487 const token = await collection.mintToken(alice);488 await testReadsYetEmptyProperties(token);690 });489 });691490692 async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {491 async function testAssignPropertiesAccordingToPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {693 await usingApi(async api => {492 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);493 await token.transfer(alice, {Substrate: charlie.address}, pieces);698494699 const propertyKeys: string[] = [];495 const propertyKeys: string[] = [];700 let i = 0;496 let i = 0;701 for (const permission of permissions) {497 for (const permission of permissions) {702 for (const signer of permission.signers) {498 i++;499 let j = 0;500 for (const signer of permission.signers) {703 const key = i + '_' + signer.address;501 j++;502 const key = i + '_' + signer.address;704 propertyKeys.push(key);503 propertyKeys.push(key);705504706 await expect(executeTransaction(505 await expect(707 api, 506 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;507 `on setting permission #${i} by alice`,508 ).to.be.fulfilled;711509712 await expect(executeTransaction(510 await expect(713 api, 511 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;512 `on adding property #${i} by signer #${j}`,717 }513 ).to.be.fulfilled;718719 i++;720 }514 }515 }721516722 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];517 const properties = await token.getProperties(propertyKeys);723 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];518 const tokenData = await token.getData();724 for (let i = 0; i < properties.length; i++) {519 for (let i = 0; i < properties.length; i++) {725 expect(properties[i].value).to.be.equal('Serotonin increase');520 expect(properties[i].value).to.be.equal('Serotonin increase');726 expect(tokensData[i].value).to.be.equal('Serotonin increase');521 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');727 }522 }728 });729 }523 }524730 it('Assigns properties to a token according to permissions (NFT)', async () => {525 itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) => {731 await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);526 const collection = await helper.nft.mintCollection(alice);527 const token = await collection.mintToken(alice);528 await testAssignPropertiesAccordingToPermissions(token, 1n);732 });529 });733 it('Assigns properties to a token according to permissions (ReFungible)', async function() {734 await requirePallets(this, [Pallets.ReFungible]);735530736 await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);531 itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {532 const collection = await helper.rft.mintCollection(alice);533 const token = await collection.mintToken(alice, 100n);534 await testAssignPropertiesAccordingToPermissions(token, 100n);737 });535 });738536739 async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {537 async function testChangesPropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {740 await usingApi(async api => {538 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);539 await token.transfer(alice, {Substrate: charlie.address}, pieces);745540746 const propertyKeys: string[] = [];541 const propertyKeys: string[] = [];747 let i = 0;542 let i = 0;748 for (const permission of permissions) {543 for (const permission of permissions) {749 if (!permission.permission.mutable) continue;544 i++;545 if (!permission.permission.mutable) continue;750 546 751 for (const signer of permission.signers) {547 let j = 0;548 for (const signer of permission.signers) {752 const key = i + '_' + signer.address;549 j++;550 const key = i + '_' + signer.address;753 propertyKeys.push(key);551 propertyKeys.push(key);754 552755 await expect(executeTransaction(553 await expect(756 api, 554 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;555 `on setting permission #${i} by alice`,760 556 ).to.be.fulfilled;557761 await expect(executeTransaction(558 await expect(762 api, 559 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;560 `on adding property #${i} by signer #${j}`,766 561 ).to.be.fulfilled;562767 await expect(executeTransaction(563 await expect(768 api, 564 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;565 `on changing property #${i} by signer #${j}`,772 }566 ).to.be.fulfilled;773 774 i++;775 }567 }776 568 }569777 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];570 const properties = await token.getProperties(propertyKeys);778 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];571 const tokenData = await token.getData();779 for (let i = 0; i < properties.length; i++) {572 for (let i = 0; i < properties.length; i++) {780 expect(properties[i].value).to.be.equal('Serotonin stable');573 expect(properties[i].value).to.be.equal('Serotonin stable');781 expect(tokensData[i].value).to.be.equal('Serotonin stable');574 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');782 }575 }783 });784 }576 }577785 it('Changes properties of a token according to permissions (NFT)', async () => {578 itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) => {786 await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);579 const collection = await helper.nft.mintCollection(alice);580 const token = await collection.mintToken(alice);581 await testChangesPropertiesAccordingPermission(token, 1n);787 });582 });788 it('Changes properties of a token according to permissions (ReFungible)', async function() {789 await requirePallets(this, [Pallets.ReFungible]);790583791 await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);584 itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {585 const collection = await helper.rft.mintCollection(alice);586 const token = await collection.mintToken(alice, 100n);587 await testChangesPropertiesAccordingPermission(token, 100n);792 });588 });793589794 async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {590 async function testDeletePropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {795 await usingApi(async api => {591 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);592 await token.transfer(alice, {Substrate: charlie.address}, pieces);800593801 const propertyKeys: string[] = [];594 const propertyKeys: string[] = [];802 let i = 0;595 let i = 0;803 596804 for (const permission of permissions) {597 for (const permission of permissions) {805 if (!permission.permission.mutable) continue;598 i++;599 if (!permission.permission.mutable) continue;806 600 807 for (const signer of permission.signers) {601 let j = 0;602 for (const signer of permission.signers) {808 const key = i + '_' + signer.address;603 j++;604 const key = i + '_' + signer.address;809 propertyKeys.push(key);605 propertyKeys.push(key);810 606811 await expect(executeTransaction(607 await expect(812 api, 608 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;609 `on setting permission #${i} by alice`,816 610 ).to.be.fulfilled;611817 await expect(executeTransaction(612 await expect(818 api, 613 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;614 `on adding property #${i} by signer #${j}`,822 615 ).to.be.fulfilled;616823 await expect(executeTransaction(617 await expect(824 api, 618 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;619 `on deleting property #${i} by signer #${j}`,828 }620 ).to.be.fulfilled;829 830 i++;831 }621 }832 622 }833 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];623834 expect(properties).to.be.empty;624 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);625 expect((await token.getData())!.properties).to.be.empty;838 });839 }626 }840 it('Deletes properties of a token according to permissions (NFT)', async () => {627 628 itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => {841 await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);629 const collection = await helper.nft.mintCollection(alice);630 const token = await collection.mintToken(alice);631 await testDeletePropertiesAccordingPermission(token, 1n);842 });632 });843 it('Deletes properties of a token according to permissions (ReFungible)', async function() {844 await requirePallets(this, [Pallets.ReFungible]);845633846 await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);634 itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {635 const collection = await helper.rft.mintCollection(alice);636 const token = await collection.mintToken(alice, 100n);637 await testDeletePropertiesAccordingPermission(token, 100n);847 });638 });848639849 it('Assigns properties to a nested token according to permissions', async () => {640 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'}});641 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');642 const collectionB = await helper.nft.mintCollection(alice);854 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});643 const targetToken = await collectionA.mintToken(alice);855 await addCollectionAdminExpectSuccess(alice, collection, bob.address);644 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());856 await transferExpectSuccess(collection, token, alice, charlie);857645858 const propertyKeys: string[] = [];646 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;647 await targetToken.transfer(alice, {Substrate: charlie.address});863 propertyKeys.push(key);864648865 await expect(executeTransaction(649 const propertyKeys: string[] = [];650 let i = 0;651 for (const permission of permissions) {652 i++;653 let j = 0;654 for (const signer of permission.signers) {866 api, 655 j++;656 const key = i + '_' + signer.address;867 alice, 657 propertyKeys.push(key);658 868 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 659 await expect(660 nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 869 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;661 `on setting permission #${i} by alice`,662 ).to.be.fulfilled;870663871 await expect(executeTransaction(664 await expect(872 api, 665 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;666 `on adding property #${i} by signer #${j}`,876 }667 ).to.be.fulfilled;877878 i++;879 }668 }880669881 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];670 }671672 const properties = await nestedToken.getProperties(propertyKeys);882 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];673 const tokenData = await nestedToken.getData();883 for (let i = 0; i < properties.length; i++) {674 for (let i = 0; i < properties.length; i++) {884 expect(properties[i].value).to.be.equal('Serotonin increase');675 expect(properties[i].value).to.be.equal('Serotonin increase');885 expect(tokensData[i].value).to.be.equal('Serotonin increase');676 expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');886 }677 }887 });678 expect(await targetToken.getProperties()).to.be.empty;888 });679 });889680890 it('Changes properties of a nested token according to permissions', async () => {681 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'}});682 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');683 const collectionB = await helper.nft.mintCollection(alice);895 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});684 const targetToken = await collectionA.mintToken(alice);896 await addCollectionAdminExpectSuccess(alice, collection, bob.address);685 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());897 await transferExpectSuccess(collection, token, alice, charlie);898686899 const propertyKeys: string[] = [];687 await collectionB.addAdmin(alice, {Substrate: bob.address});900 let i = 0;901 for (const permission of permissions) {902 if (!permission.permission.mutable) continue;903 688 await targetToken.transfer(alice, {Substrate: charlie.address});904 for (const signer of permission.signers) {905 const key = i + '_' + signer.address;906 propertyKeys.push(key);907689908 await expect(executeTransaction(690 const propertyKeys: string[] = [];691 let i = 0;909 api, 692 for (const permission of permissions) {693 i++;910 alice, 694 if (!permission.permission.mutable) continue;695 911 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 696 let j = 0;697 for (const signer of permission.signers) {912 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;698 j++;699 const key = i + '_' + signer.address;700 propertyKeys.push(key);913701914 await expect(executeTransaction(702 await expect(915 api, 703 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;704 `on setting permission #${i} by alice`,705 ).to.be.fulfilled;919706920 await expect(executeTransaction(707 await expect(921 api, 708 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;709 `on adding property #${i} by signer #${j}`,925 }710 ).to.be.fulfilled;926711927 i++;712 await expect(713 nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 714 `on changing property #${i} by signer #${j}`,715 ).to.be.fulfilled;928 }716 }717 }929718930 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];719 const properties = await nestedToken.getProperties(propertyKeys);931 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];720 const tokenData = await nestedToken.getData();932 for (let i = 0; i < properties.length; i++) {721 for (let i = 0; i < properties.length; i++) {933 expect(properties[i].value).to.be.equal('Serotonin stable');722 expect(properties[i].value).to.be.equal('Serotonin stable');934 expect(tokensData[i].value).to.be.equal('Serotonin stable');723 expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');935 }724 }936 });725 expect(await targetToken.getProperties()).to.be.empty;937 });726 });938727939 it('Deletes properties of a nested token according to permissions', async () => {728 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'}});729 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');730 const collectionB = await helper.nft.mintCollection(alice);944 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});731 const targetToken = await collectionA.mintToken(alice);945 await addCollectionAdminExpectSuccess(alice, collection, bob.address);732 const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());946 await transferExpectSuccess(collection, token, alice, charlie);947733948 const propertyKeys: string[] = [];734 await collectionB.addAdmin(alice, {Substrate: bob.address});949 let i = 0;735 await targetToken.transfer(alice, {Substrate: charlie.address});950736951 for (const permission of permissions) {737 const propertyKeys: string[] = [];738 let i = 0;739 for (const permission of permissions) {952 if (!permission.permission.mutable) continue;740 i++;741 if (!permission.permission.mutable) continue;953 742 954 for (const signer of permission.signers) {743 let j = 0;744 for (const signer of permission.signers) {955 const key = i + '_' + signer.address;745 j++;746 const key = i + '_' + signer.address;956 propertyKeys.push(key);747 propertyKeys.push(key);957748958 await expect(executeTransaction(749 await expect(959 api, 750 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;751 `on setting permission #${i} by alice`,752 ).to.be.fulfilled;963753964 await expect(executeTransaction(754 await expect(965 api, 755 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;756 `on adding property #${i} by signer #${j}`,757 ).to.be.fulfilled;969758970 await expect(executeTransaction(759 await expect(971 api, 760 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;761 `on deleting property #${i} by signer #${j}`,975 }762 ).to.be.fulfilled;976 977 i++;978 }763 }764 }979765980 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];766 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[];767 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);768 expect(await targetToken.getProperties()).to.be.empty;985 });986 });769 });987});770});988771989describe('Negative Integration Test: Token Properties', () => {772describe('Negative Integration Test: Token Properties', () => {990 let collection: number;773 let alice: IKeyringPair; // collection owner991 let token: number;774 let bob: IKeyringPair; // collection admin992 let originalSpace: number;775 let charlie: IKeyringPair; // token owner776993 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];777 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];994778995 before(async () => {779 before(async () => {996 await usingApi(async (api, privateKeyWrapper) => {780 await usingPlaygrounds(async (helper, privateKey) => {997 alice = privateKeyWrapper('//Alice');781 const donor = privateKey('//Alice');998 bob = privateKeyWrapper('//Bob');782 let dave: IKeyringPair;999 charlie = privateKeyWrapper('//Charlie');783 [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);1000 const dave = privateKeyWrapper('//Dave');1001784785 // todo:playgrounds probably separate these tests later1002 constitution = [786 constitution = [1003 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},787 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},1004 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},788 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},1010 });794 });1011 });795 });10127961013 async function prepare(mode: CollectionMode, pieces: number) {797 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;798 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 }799 }10428001043 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {801 async function prepare(token: UniqueNFToken | UniqueRFToken, pieces: bigint): Promise<number> {1044 await prepare(mode, pieces);802 await token.collection.addAdmin(alice, {Substrate: bob.address});1045 803 await token.transfer(alice, {Substrate: charlie.address}, pieces);1046 await usingApi(async api => {8041047 let i = -1;805 let i = 0;1048 for (const forbiddance of constitution) {806 for (const passage of constitution) {1049 i++;807 i++;1050 if (!forbiddance.permission.mutable) continue;808 const signer = passage.signers[0];1051 809 1052 await expect(executeTransaction(810 await expect(1053 api, 811 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/);812 `on setting permission ${i} by alice`,1057 813 ).to.be.fulfilled;8141058 await expect(executeTransaction(815 await expect(1059 api, 816 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/);817 `on adding property ${i} by ${signer.address}`,818 ).to.be.fulfilled;1063 }819 }1064 8201065 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();821 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 });822 return originalSpace;1068 }823 }8241069 it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {825 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {826 const originalSpace = await prepare(token, pieces);827828 let i = 0;829 for (const forbiddance of constitution) {830 i++;831 if (!forbiddance.permission.mutable) continue;832833 await expect(834 token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 835 `on failing to change property ${i} by the malefactor`,836 ).to.be.rejectedWith(/common\.NoPermission/);837838 await expect(839 token.deleteProperties(forbiddance.sinner, [`${i}`]), 840 `on failing to delete property ${i} by the malefactor`,841 ).to.be.rejectedWith(/common\.NoPermission/);842 }843844 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 845 expect(consumedSpace).to.be.equal(originalSpace);846 }847848 itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) => {1070 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);849 const collection = await helper.nft.mintCollection(alice);850 const token = await collection.mintToken(alice);851 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);1071 });852 });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]);10748531075 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);854 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {855 const collection = await helper.rft.mintCollection(alice);856 const token = await collection.mintToken(alice, 100n);857 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);1076 });858 });10778591078 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {860 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {1079 await prepare(mode, pieces);861 const originalSpace = await prepare(token, pieces);8621080 863 let i = 0;1081 await usingApi(async api => {864 for (const permission of constitution) {1082 let i = -1;865 i++;866 if (permission.permission.mutable) continue;8671083 for (const permission of constitution) {868 await expect(869 token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 1084 i++;870 `on failing to change property ${i} by signer #0`,871 ).to.be.rejectedWith(/common\.NoPermission/);8721085 if (permission.permission.mutable) continue;873 await expect(874 token.deleteProperties(permission.signers[0], [i.toString()]), 875 `on failing to delete property ${i} by signer #0`,876 ).to.be.rejectedWith(/common\.NoPermission/);877 }1086 878 1087 await expect(executeTransaction(879 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 880 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 }881 }8821104 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {883 itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) => {1105 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);884 const collection = await helper.nft.mintCollection(alice);885 const token = await collection.mintToken(alice);886 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);1106 });887 });1107 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async function() {1108 await requirePallets(this, [Pallets.ReFungible]);11098881110 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);889 itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {890 const collection = await helper.rft.mintCollection(alice);891 const token = await collection.mintToken(alice, 100n);892 await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);1111 });893 });11128941113 async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {895 async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {1114 await prepare(mode, pieces);896 const originalSpace = await prepare(token, pieces);11158971116 await usingApi(async api => {898 await expect(1117 await expect(executeTransaction(1118 api, 899 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/);900 'on failing to add a previously non-existent property',901 ).to.be.rejectedWith(/common\.NoPermission/);1122 902 1123 await expect(executeTransaction(903 await expect(1124 api, 904 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;905 'on setting a new non-permitted property',1128 906 ).to.be.fulfilled;9071129 await expect(executeTransaction(908 await expect(1130 api, 909 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/);910 'on failing to add a property forbidden by the \'None\' permission',1134 911 ).to.be.rejectedWith(/common\.NoPermission/);9121135 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;913 expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;1136 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();914 915 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 1137 expect(properties.consumedSpace).to.be.equal(originalSpace);916 expect(consumedSpace).to.be.equal(originalSpace);1138 });1139 }917 }9181140 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {919 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);920 const collection = await helper.nft.mintCollection(alice);921 const token = await collection.mintToken(alice);922 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);1142 });923 });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]);11459241146 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);925 itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {926 const collection = await helper.rft.mintCollection(alice);927 const token = await collection.mintToken(alice, 100n);928 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);1147 });929 });11489301149 async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {931 async function testForbidsAddingTooManyProperties(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {1150 await prepare(mode, pieces);932 const originalSpace = await prepare(token, pieces);11519331152 await usingApi(async api => {934 await expect(1153 await expect(executeTransaction(1154 api, 935 token.collection.setTokenPropertyPermissions(alice, [1155 alice, 1156 api.tx.unique.setTokenPropertyPermissions(collection, [1157 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 936 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 1158 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},937 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},1159 ]), 938 ]), 939 'on setting new permissions for properties',940 ).to.be.fulfilled;941942 // Mute the general tx parsing error943 {944 console.error = () => {};945 await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))946 .to.be.rejected;947 }948949 await expect(token.setProperties(alice, [1160 ), 'on setting a new non-permitted property').to.not.be.rejected;950 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 951 {key: 'young_years', value: 'neverending'.repeat(1490)},952 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);1161 953 1162 // Mute the general tx parsing error954 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 }955 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;956 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 }957 }9581186 it('Forbids adding too many properties to a token (NFT)', async () => {959 itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => {1187 await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);960 const collection = await helper.nft.mintCollection(alice);961 const token = await collection.mintToken(alice);962 await testForbidsAddingTooManyProperties(token, 1n);1188 });963 });1189 it('Forbids adding too many properties to a token (ReFungible)', async function() {1190 await requirePallets(this, [Pallets.ReFungible]);11919641192 await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);965 itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {966 const collection = await helper.rft.mintCollection(alice);967 const token = await collection.mintToken(alice, 100n);968 await testForbidsAddingTooManyProperties(token, 100n);1193 });969 });1194});970});11959711196describe('ReFungible token properties permissions tests', () => {972describe('ReFungible token properties permissions tests', () => {1197 let collection: number;973 let alice: IKeyringPair;1198 let token: number;974 let bob: IKeyringPair;975 let charlie: IKeyringPair;11999761200 before(async function() {977 before(async function() {1201 await requirePallets(this, [Pallets.ReFungible]);978 await usingPlaygrounds(async (helper, privateKey) => {979 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);12029801203 await usingApi(async (api, privateKeyWrapper) => {981 const donor = privateKey('//Alice');1204 alice = privateKeyWrapper('//Alice');1205 bob = privateKeyWrapper('//Bob');982 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);1206 charlie = privateKeyWrapper('//Charlie');1207 });983 });1208 });984 });12099851210 beforeEach(async () => {986 async function prepare(helper: UniqueHelper): Promise<UniqueRFToken> {1211 await usingApi(async api => {987 const collection = await helper.rft.mintCollection(alice);988 const token = await collection.mintToken(alice, 100n);1212 collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});989 990 await collection.addAdmin(alice, {Substrate: bob.address});1213 token = await createItemExpectSuccess(alice, collection, 'ReFungible');991 await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);1214 await addCollectionAdminExpectSuccess(alice, collection, bob.address);992 993 return token;994 }12159951216 await expect(executeTransaction(996 itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1217 api, 1218 alice, 997 const token = await prepare(helper);9981219 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 999 await token.transfer(alice, {Substrate: charlie.address}, 33n);10001001 await expect(token.setProperties(alice, [1002 {key: 'fractals', value: 'multiverse'}, 1220 )).to.not.be.rejected;1003 ])).to.be.rejectedWith(/common\.NoPermission/);1221 });1222 });1004 });122310051224 it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {1006 itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) => {1225 await usingApi(async api => {1007 const token = await prepare(helper);10081226 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1009 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1227 1010 .to.be.fulfilled;10111228 await expect(executeTransaction(1012 await expect(token.setProperties(alice, [1229 api, 1013 {key: 'fractals', value: 'multiverse'}, 1230 alice, 1014 ])).to.be.fulfilled;10151231 api.tx.unique.setTokenProperties(collection, token, [1016 await token.transfer(alice, {Substrate: charlie.address}, 33n);10171018 await expect(token.setProperties(alice, [1232 {key: 'key', value: 'word'}, 1019 {key: 'fractals', value: 'want to rule the world'}, 1233 ]), 1234 )).to.be.rejectedWith(/common\.NoPermission/);1020 ])).to.be.rejectedWith(/common\.NoPermission/);1235 });1236 });1021 });123710221238 it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {1023 itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => {1239 await usingApi(async api => {1024 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;125310251254 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1026 await expect(token.setProperties(alice, [1255 1027 {key: 'fractals', value: 'one headline - why believe it'}, 1256 await expect(executeTransaction(1257 api, 1258 alice, 1259 api.tx.unique.setTokenProperties(collection, token, [1028 ])).to.be.fulfilled;10291260 {key: 'key', value: 'bad word'}, 1030 await token.transfer(alice, {Substrate: charlie.address}, 33n);10311261 ]), 1032 await expect(token.deleteProperties(alice, ['fractals'])).1262 )).to.be.rejectedWith(/common\.NoPermission/);1033 to.be.rejectedWith(/common\.NoPermission/);1263 });1264 });1034 });126510351266 it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {1036 itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) => {1267 await usingApi(async api => {1037 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;127510381276 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1039 await token.transfer(alice, {Substrate: charlie.address}, 33n);1277 10401278 await expect(executeTransaction(1041 await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1279 api, 1280 alice, 1042 .to.be.fulfilled;10431281 api.tx.unique.deleteTokenProperties(collection, token, [1044 await expect(token.setProperties(alice, [1282 'key',1045 {key: 'fractals', value: 'multiverse'}, 1283 ]), 1284 )).to.be.rejectedWith(/common\.NoPermission/);1046 ])).to.be.fulfilled;1285 });1286 });1047 });1287});1048});12881049tests/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.nestingAccount());383639 // Unnest37 // Unnest40 await expect(executeTransaction(38 await expect(nestedToken.transferFrom(alice, targetToken.nestingAccount(), {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.nestingAccount()), '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.nestingAccount());61 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});62 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');55 await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {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.nestingAccount())).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.nestingAccount(), 5n);74 const balanceBefore = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);61 await expect(collectionFT.burnTokensFrom(alice, targetToken.nestingAccount(), 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.nestingAccount())).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.nestingAccount());9697 // Nest and unnest98 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');75 await expect(token.transferFrom(alice, targetToken.nestingAccount(), {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.nestingAccount())).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.nestingAccount(), 5n);103 await expect(executeTransaction(81 await expect(token.burnFrom(alice, targetToken.nestingAccount(), 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.nestingAccount())).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.nestingAccount());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.nestingAccountInLowerCase());139109140 // Try to burn110 // Try to burn141 await expect(executeTransaction(111 await expect(nestedToken.burnFrom(bob, targetToken.nestingAccount())).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.nestingAccountInLowerCase());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.nestingAccount());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/index.tsdiffbeforeafterbothno syntactic changes
tests/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}108108tests/src/util/playgrounds/unique.tsdiffbeforeafterboth55 RPC: 'rpc',55 RPC: 'rpc',56 };56 };575758 static getNestingTokenAddress(collectionId: number, tokenId: number) {58 static getTokenAccount(token: IToken): ICrossAccountId {59 return nesting.tokenIdToAddress(collectionId, tokenId);59 return {Ethereum: this.getTokenAddress(token)};60 }60 }616162 static getTokenAccountInLowerCase(token: IToken): ICrossAccountId {63 return {Ethereum: this.getTokenAddress(token).toLowerCase()};64 }6566 static getTokenAddress(token: IToken): string {67 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);68 }6962 static getDefaultLogger(): ILogger {70 static getDefaultLogger(): ILogger {63 return {71 return {64 log(msg: any, level = 'INFO') {72 log(msg: any, level = 'INFO') {592 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();600 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();593601594 return normalize602 return normalize595 ? admins.map((address: any) => {603 ? admins.map((address: any) => this.helper.address.normalizeCrossAccountIfSubstrate(address))596 return address.Substrate597 ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)}598 : address;599 })600 : admins;604 : admins;601 }605 }602606610 async getAllowList(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {614 async getAllowList(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {611 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();615 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();612 return normalize616 return normalize613 ? allowListed.map((address: any) => {617 ? allowListed.map((address: any) => this.helper.address.normalizeCrossAccountIfSubstrate(address))614 return address.Substrate615 ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)}616 : address;617 })618 : allowListed;618 : allowListed;619 }619 }620620897 }897 }898898899 /**899 /**900 * Get collection properties.901 * 902 * @param collectionId ID of collection903 * @param propertyKeys optionally filter the returned properties to only these keys904 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);905 * @returns array of key-value pairs906 */907 async getProperties(collectionId: number, propertyKeys: string[] | null = null): Promise<IProperty[]> {908 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();909 }910911 /**900 * Deletes onchain properties from the collection.912 * Deletes onchain properties from the collection.901 *913 *902 * @param signer keyring of signer914 * @param signer keyring of signer988 *1000 *989 * @param signer keyring of signer1001 * @param signer keyring of signer990 * @param collectionId ID of collection1002 * @param collectionId ID of collection1003 * @param tokenId ID of token991 * @param fromAddressObj address on behalf of which the token will be burnt1004 * @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 1n1005 * @param amount amount of tokens to be burned. For NFT must be set to 1n994 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1006 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})995 * @returns ```true``` if extrinsic success, otherwise ```false```1007 * @returns ```true``` if extrinsic success, otherwise ```false```996 */1008 */997 async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, amount=1n): Promise<boolean> {1009 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {998 const burnResult = await this.helper.executeExtrinsic(1010 const burnResult = await this.helper.executeExtrinsic(999 signer,1011 signer,1000 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1012 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1106 if (tokenData === null || tokenData.owner === null) return null;1118 if (tokenData === null || tokenData.owner === null) return null;1107 const owner = {} as any;1119 const owner = {} as any;1108 for (const key of Object.keys(tokenData.owner)) {1120 for (const key of Object.keys(tokenData.owner)) {1109 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1121 owner[key.toLocaleLowerCase()] = this.helper.address.normalizeCrossAccountIfSubstrate(tokenData.owner[key]);1110 }1122 }1111 tokenData.normalizedOwner = crossAccountIdFromLower(owner);1123 tokenData.normalizedOwner = crossAccountIdFromLower(owner);1112 return tokenData;1124 return tokenData;1117 *1129 *1118 * @param signer keyring of signer1130 * @param signer keyring of signer1119 * @param collectionId ID of collection1131 * @param collectionId ID of collection1120 * @param permissions permissions to change a property by the collection owner or admin1132 * @param permissions permissions to change a property by the collection admin or token owner1121 * @example setTokenPropertyPermissions(1133 * @example setTokenPropertyPermissions(1122 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1134 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1123 * )1135 * )1134 }1146 }113511471136 /**1148 /**1149 * Get token property permissions.1150 * 1151 * @param collectionId ID of collection1152 * @param propertyKeys optionally filter the returned property permissions to only these keys1153 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1154 * @returns array of key-permission pairs1155 */1156 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1157 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1158 }11591160 /**1137 * Set token properties1161 * Set token properties1138 *1162 *1139 * @param signer keyring of signer1163 * @param signer keyring of signer1154 }1178 }115511791156 /**1180 /**1181 * Get properties, metadata assigned to a token.1182 * 1183 * @param collectionId ID of collection1184 * @param tokenId ID of token1185 * @param propertyKeys optionally filter the returned properties to only these keys1186 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1187 * @returns array of key-value pairs1188 */1189 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys: string[] | null = null): Promise<IProperty[]> {1190 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1191 }11921193 /**1157 * Delete the provided properties of a token1194 * Delete the provided properties of a token1158 * @param signer keyring of signer1195 * @param signer keyring of signer1159 * @param collectionId ID of collection1196 * @param collectionId ID of collection1181 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1218 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1182 * @returns object of the created collection1219 * @returns object of the created collection1183 */1220 */1184 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueCollectionBase> {1221 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1185 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1222 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1186 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1223 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1187 for (const key of ['name', 'description', 'tokenPrefix']) {1224 for (const key of ['name', 'description', 'tokenPrefix']) {1223 * @example getTokenObject(10, 5);1260 * @example getTokenObject(10, 5);1224 * @returns instance of UniqueNFTToken1261 * @returns instance of UniqueNFTToken1225 */1262 */1226 getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1263 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1227 return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1264 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1228 }1265 }122912661230 /**1267 /**130413411305 if (owner === null) return null;1342 if (owner === null) return null;130613431307 owner = owner.toHuman();1344 return owner.toHuman();13081309 return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1310 }1345 }131113461312 /**1347 /**1339 * @returns ```true``` if extrinsic success, otherwise ```false```1374 * @returns ```true``` if extrinsic success, otherwise ```false```1340 */1375 */1341 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1376 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1342 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1377 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1343 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1378 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1344 if(!result) {1379 if(!result) {1345 throw Error('Unable to nest token!');1380 throw Error('Unable to nest token!');1357 * @returns ```true``` if extrinsic success, otherwise ```false```1392 * @returns ```true``` if extrinsic success, otherwise ```false```1358 */1393 */1359 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1394 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1360 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1395 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1361 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1396 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1362 if(!result) {1397 if(!result) {1363 throw Error('Unable to unnest token!');1398 throw Error('Unable to unnest token!');1377 * })1412 * })1378 * @returns object of the created collection1413 * @returns object of the created collection1379 */1414 */1380 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise<UniqueNFTCollection> {1415 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1381 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1416 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1382 }1417 }138314181387 * @param data token data1422 * @param data token data1388 * @returns created token object1423 * @returns created token object1389 */1424 */1390 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFTToken> {1425 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1391 const creationResult = await this.helper.executeExtrinsic(1426 const creationResult = await this.helper.executeExtrinsic(1392 signer,1427 signer,1393 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1428 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1418 * }]);1453 * }]);1419 * @returns ```true``` if extrinsic success, otherwise ```false```1454 * @returns ```true``` if extrinsic success, otherwise ```false```1420 */1455 */1421 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFTToken[]> {1456 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1422 const creationResult = await this.helper.executeExtrinsic(1457 const creationResult = await this.helper.executeExtrinsic(1423 signer,1458 signer,1424 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1459 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1446 * }]);1481 * }]);1447 * @returns array of newly created tokens1482 * @returns array of newly created tokens1448 */1483 */1449 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFTToken[]> {1484 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1450 const rawTokens = [];1485 const rawTokens = [];1451 for (const token of tokens) {1486 for (const token of tokens) {1452 const raw = {NFT: {properties: token.properties}};1487 const raw = {NFT: {properties: token.properties}};1495 * @example getTokenObject(10, 5);1530 * @example getTokenObject(10, 5);1496 * @returns instance of UniqueNFTToken1531 * @returns instance of UniqueNFTToken1497 */1532 */1498 getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1533 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1499 return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1534 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1500 }1535 }150115361502 /**1537 /**1563 * })1598 * })1564 * @returns object of the created collection1599 * @returns object of the created collection1565 */1600 */1566 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise<UniqueRFTCollection> {1601 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1567 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1602 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1568 }1603 }156916041574 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1609 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1575 * @returns created token object1610 * @returns created token object1576 */1611 */1577 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFTToken> {1612 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1578 const creationResult = await this.helper.executeExtrinsic(1613 const creationResult = await this.helper.executeExtrinsic(1579 signer,1614 signer,1580 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1615 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1591 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1626 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1592 }1627 }159316281594 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFTToken[]> {1629 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1595 throw Error('Not implemented');1630 throw Error('Not implemented');1596 const creationResult = await this.helper.executeExtrinsic(1631 const creationResult = await this.helper.executeExtrinsic(1597 signer,1632 signer,1611 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1646 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1612 * @returns array of newly created RFT tokens1647 * @returns array of newly created RFT tokens1613 */1648 */1614 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFTToken[]> {1649 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1615 const rawTokens = [];1650 const rawTokens = [];1616 for (const token of tokens) {1651 for (const token of tokens) {1617 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1652 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1633 * @param tokenId ID of token1668 * @param tokenId ID of token1634 * @param amount number of pieces to be burnt1669 * @param amount number of pieces to be burnt1635 * @example burnToken(aliceKeyring, 10, 5);1670 * @example burnToken(aliceKeyring, 10, 5);1636 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1671 * @returns ```true``` and burnt token number, if extrinsic is successful. Otherwise ```false``` and ```null```1637 */1672 */1638 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<{ success: boolean; token: number | null; }> {1673 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);1674 return await super.burnToken(signer, collectionId, tokenId, amount);1640 }1675 }164116761642 /**1677 /**1678 * Destroys a concrete instance of RFT on behalf of the owner.1679 * @param signer keyring of signer1680 * @param collectionId ID of collection1681 * @param tokenId ID of token1682 * @param fromAddressObj address on behalf of which the token will be burnt1683 * @param amount number of pieces to be burnt1684 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1685 * @returns ```true``` if extrinsic success, otherwise ```false```1686 */1687 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1688 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1689 }16901691 /**1643 * Set, change, or remove approved address to transfer the ownership of the RFT.1692 * Set, change, or remove approved address to transfer the ownership of the RFT.1644 *1693 *1645 * @param signer keyring of signer1694 * @param signer keyring of signer1711 * }, 18)1760 * }, 18)1712 * @returns newly created fungible collection1761 * @returns newly created fungible collection1713 */1762 */1714 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0): Promise<UniqueFTCollection> {1763 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1715 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1764 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1716 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1765 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1717 collectionOptions.mode = {fungible: decimalPoints};1766 collectionOptions.mode = {fungible: decimalPoints};1840 * @returns ```true``` if extrinsic success, otherwise ```false```1889 * @returns ```true``` if extrinsic success, otherwise ```false```1841 */1890 */1842 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1891 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1843 return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, amount);1892 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);1844 }1893 }184518941846 /**1895 /**193619851937class BalanceGroup extends HelperGroup {1986class BalanceGroup extends HelperGroup {1938 /**1987 /**1939 * Representation of the native token in the smallest unit1988 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).1940 * @example getOneTokenNominal()1989 * @example getOneTokenNominal()1941 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1990 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1942 */1991 */2017 }2066 }201820672019 /**2068 /**2069 * Normalizes the address of an account ONLY if it's Substrate to the specified ss58 format, by default ```42```.2070 * @param account account of either Substrate type or Ethereum, but only Substrate will be changed2071 * @param ss58Format format for address conversion, by default ```42```2072 * @example normalizeCrossAccountIfSubstrate({Substrate: "unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx"}) // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2073 * @returns untouched ethereum account or substrate account converted to normalized (i.e., starting with 5) or specified explicitly representation2074 */2075 normalizeCrossAccountIfSubstrate(account: ICrossAccountId, ss58Format = 42): ICrossAccountId {2076 return account.Substrate2077 ? {Substrate: this.normalizeSubstrate(account.Substrate, ss58Format)}2078 : account;2079 }20802081 /**2020 * Get address in the connected chain format2082 * Get address in the connected chain format2021 * @param address substrate address2083 * @param address substrate address2022 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2084 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2161}2223}21622224216322252164class UniqueCollectionBase {2226export class UniqueBaseCollection {2165 helper: UniqueHelper;2227 helper: UniqueHelper;2166 collectionId: number;2228 collectionId: number;216722292194 return await this.helper.collection.getEffectiveLimits(this.collectionId);2256 return await this.helper.collection.getEffectiveLimits(this.collectionId);2195 }2257 }219622582259 async getProperties(propertyKeys: string[] | null = null) {2260 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);2261 }22622263 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2264 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2265 }22662197 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {2267 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {2198 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);2268 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);2199 }2269 }2238 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);2308 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);2239 }2309 }224023102241 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2242 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2243 }22442245 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {2311 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {2246 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);2312 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);2247 }2313 }2260}2326}22612327226223282263class UniqueNFTCollection extends UniqueCollectionBase {2329export class UniqueNFTCollection extends UniqueBaseCollection {2264 getTokenObject(tokenId: number) {2330 getTokenObject(tokenId: number) {2265 return new UniqueNFTToken(tokenId, this);2331 return new UniqueNFToken(tokenId, this);2266 }2332 }226723332268 async getTokensByAddress(addressObj: ICrossAccountId) {2334 async getTokensByAddress(addressObj: ICrossAccountId) {2285 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2351 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2286 }2352 }228723532354 async getPropertyPermissions(propertyKeys: string[] | null = null) {2355 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);2356 }23572358 async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) {2359 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);2360 }23612288 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2362 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2289 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2363 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2290 }2364 }2313 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);2387 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);2314 }2388 }231523892390 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {2391 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);2392 }23932316 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2394 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2317 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);2395 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);2318 }2396 }2335}2413}23362414233724152338class UniqueRFTCollection extends UniqueCollectionBase {2416export class UniqueRFTCollection extends UniqueBaseCollection {2339 getTokenObject(tokenId: number) {2417 getTokenObject(tokenId: number) {2340 return new UniqueRFTToken(tokenId, this);2418 return new UniqueRFToken(tokenId, this);2341 }2419 }234224202421 async getToken(tokenId: number, blockHashAt?: string) {2422 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);2423 }24242343 async getTokensByAddress(addressObj: ICrossAccountId) {2425 async getTokensByAddress(addressObj: ICrossAccountId) {2344 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2426 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2345 }2427 }2356 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2438 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2357 }2439 }235824402441 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2442 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2443 }24442445 async getPropertyPermissions(propertyKeys: string[] | null = null) {2446 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);2447 }24482449 async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) {2450 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);2451 }24522359 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {2453 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {2360 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2454 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2361 }2455 }2368 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);2462 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);2369 }2463 }237024642371 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2372 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2373 }23742375 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {2465 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {2376 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);2466 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);2377 }2467 }2388 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);2478 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);2389 }2479 }239024802481 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {2482 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);2483 }24842391 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2485 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2392 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);2486 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);2393 }2487 }2402}2496}24032497240424982405class UniqueFTCollection extends UniqueCollectionBase {2499export class UniqueFTCollection extends UniqueBaseCollection {2406 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {2500 async getBalance(addressObj: ICrossAccountId) {2407 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);2501 return await this.helper.ft.getBalance(this.collectionId, addressObj);2408 }2502 }240925032410 async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {2504 async getTotalPieces() {2411 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);2505 return await this.helper.ft.getTotalPieces(this.collectionId);2412 }2506 }241325072414 async getBalance(addressObj: ICrossAccountId) {2508 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2415 return await this.helper.ft.getBalance(this.collectionId, addressObj);2509 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2416 }2510 }241725112418 async getTop10Owners() {2512 async getTop10Owners() {2419 return await this.helper.ft.getTop10Owners(this.collectionId);2513 return await this.helper.ft.getTop10Owners(this.collectionId);2420 }2514 }242125152516 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {2517 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);2518 }25192520 async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {2521 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);2522 }25232422 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {2524 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {2423 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2525 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2424 }2526 }2435 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);2537 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);2436 }2538 }243725392438 async getTotalPieces() {2439 return await this.helper.ft.getTotalPieces(this.collectionId);2440 }24412442 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {2540 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {2443 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);2541 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);2444 }2542 }24452446 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2447 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2448 }2449}2543}24502544245125452452class UniqueTokenBase implements IToken {2546export class UniqueBaseToken {2453 collection: UniqueNFTCollection | UniqueRFTCollection;2547 collection: UniqueNFTCollection | UniqueRFTCollection;2454 collectionId: number;2548 collectionId: number;2455 tokenId: number;2549 tokenId: number;2464 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2558 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2465 }2559 }246625602561 async getProperties(propertyKeys: string[] | null = null) {2562 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);2563 }25642467 async setProperties(signer: TSigner, properties: IProperty[]) {2565 async setProperties(signer: TSigner, properties: IProperty[]) {2468 return await this.collection.setTokenProperties(signer, this.tokenId, properties);2566 return await this.collection.setTokenProperties(signer, this.tokenId, properties);2469 }2567 }247025682471 async deleteProperties(signer: TSigner, propertyKeys: string[]) {2569 async deleteProperties(signer: TSigner, propertyKeys: string[]) {2472 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);2570 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);2473 }2571 }25722573 nestingAccount() {2574 return this.collection.helper.util.getTokenAccount(this);2575 }25762577 nestingAccountInLowerCase() {2578 return this.collection.helper.util.getTokenAccountInLowerCase(this);2579 }2474}2580}24752581247625822477class UniqueNFTToken extends UniqueTokenBase {2583export class UniqueNFToken extends UniqueBaseToken {2478 collection: UniqueNFTCollection;2584 collection: UniqueNFTCollection;247925852480 constructor(tokenId: number, collection: UniqueNFTCollection) {2586 constructor(tokenId: number, collection: UniqueNFTCollection) {2525 async burn(signer: TSigner) {2631 async burn(signer: TSigner) {2526 return await this.collection.burnToken(signer, this.tokenId);2632 return await this.collection.burnToken(signer, this.tokenId);2527 }2633 }26342635 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {2636 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);2637 }2528}2638}252926392530class UniqueRFTToken extends UniqueTokenBase {2640export class UniqueRFToken extends UniqueBaseToken {2531 collection: UniqueRFTCollection;2641 collection: UniqueRFTCollection;253226422533 constructor(tokenId: number, collection: UniqueRFTCollection) {2643 constructor(tokenId: number, collection: UniqueRFTCollection) {2534 super(tokenId, collection);2644 super(tokenId, collection);2535 this.collection = collection;2645 this.collection = collection;2536 }2646 }253726472648 async getData(blockHashAt?: string) {2649 return await this.collection.getToken(this.tokenId, blockHashAt);2650 }26512538 async getTop10Owners() {2652 async getTop10Owners() {2539 return await this.collection.getTop10TokenOwners(this.tokenId);2653 return await this.collection.getTop10TokenOwners(this.tokenId);2540 }2654 }2547 return await this.collection.getTokenTotalPieces(this.tokenId);2661 return await this.collection.getTokenTotalPieces(this.tokenId);2548 }2662 }254926632664 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2665 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2666 }26672550 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {2668 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {2551 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2669 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2552 }2670 }2559 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);2677 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);2560 }2678 }256126792562 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2563 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2564 }25652566 async repartition(signer: TSigner, amount: bigint) {2680 async repartition(signer: TSigner, amount: bigint) {2567 return await this.collection.repartitionToken(signer, this.tokenId, amount);2681 return await this.collection.repartitionToken(signer, this.tokenId, amount);2568 }2682 }256926832570 async burn(signer: TSigner, amount=1n) {2684 async burn(signer: TSigner, amount=1n) {2571 return await this.collection.burnToken(signer, this.tokenId, amount);2685 return await this.collection.burnToken(signer, this.tokenId, amount);2686 }26872688 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {2689 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);2572 }2690 }2573}2691}25742692