git.delta.rocks / unique-network / refs/commits / edc6ac8eb230

difftreelog

Merge pull request #607 from UniqueNetwork/tests/nesting

ut-akuznetsov2022-09-27parents: #644729e #77e366b.patch.diff
in: master

11 files changed

modifiedtests/src/eth/util/playgrounds/index.tsdiffbeforeafterboth

no syntactic changes

modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
240 }240 }
241241
242 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 }
245245
246 normalizeAddress(address: string): string {246 normalizeAddress(address: string): string {
modifiedtests/src/nesting/graphs.test.tsdiffbeforeafterboth
1import {ApiPromise} from '@polkadot/api';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
2// This file is part of Unique Network.
3
4// Unique Network is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Unique Network is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
16
2import {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';
720
8/**21/**
9 * ```dot22 * ```dot
12 * 8 -> 525 * 8 -> 5
13 * ```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}}));
18
19 await executeTransaction(api, sender, api.tx.unique.createMultipleItemsEx(collectionId, {NFT: Array(8).fill({owner: {Substrate: sender.address}})}));
2031
21 await transferExpectSuccess(collectionId, 8, sender, tokenIdToCross(collectionId, 5));32 await tokens[7].nest(sender, tokens[4]);
22
23 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]);
26
27 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]);
3039
31 return collectionId;40 return tokens;
32}41}
3342
34describe('Graphs', () => {43describe('Graphs', () => {
44 let alice: IKeyringPair;
45
46 before(async () => {
47 await usingPlaygrounds(async (helper, privateKey) => {
48 const donor = privateKey('//Alice');
49 [alice] = await helper.arrange.createAccounts([10n], donor);
50 });
51 });
52
35 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);
4055
41 // to self56 // to self
42 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 graph
47 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});
5872
modifiedtests/src/nesting/migration-check.test.tsdiffbeforeafterboth
8import find from 'find-process';8import find from 'find-process';
99
10// todo un-skip for migrations10// todo un-skip for migrations
11// 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;
1314
14 before(async() => {15 before(async() => {
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
1import {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';
243
25let alice: IKeyringPair;4// Unique Network is free software: you can redistribute it and/or modify
26let bob: IKeyringPair;5// it under the terms of the GNU General Public License as published by
27let charlie: IKeyringPair;6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
288
9// Unique Network is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
16
17import {IKeyringPair} from '@polkadot/types/types';
18import {expect, itSub, Pallets, usingPlaygrounds} from '../util/playgrounds';
19
29describe('Integration Test: Composite nesting tests', () => {20describe('Integration Test: Composite nesting tests', () => {
21 let alice: IKeyringPair;
22 let bob: IKeyringPair;
23
30 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 });
3630
37 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);
4234
43 // Create a nested token35 // Create an immediately nested token
44 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 nested
41 const newToken = await collection.mintToken(alice);
4742
48 // Create a token to be nested43 // Nest
44 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());
5047
51 // Nest48 // Move bundle to different user
52 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());
5554
56 // Move bundle to different user55 // Unnest
57 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});
60
61 // Unnest
62 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 });
6660
67 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);
7165
72 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');66 // Create a nested token
73 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT');
74
75 // Create a nested token
76 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()});
79
80 // Transfer the nested token to another token69
70 // Transfer the nested token to another token
81 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 });
9675
97 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');
10382
104 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');83 // Create a nested NFT token
105 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');
10888
109 // Create a nested NFT token89 // Create then nest
110 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');
11696
117 // Create then nest97 // Move token B to a different user outside the nesting tree
118 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');
126102
127 // Move token B to a different user outside the nesting tree103 // Create a fungible token in another collection and then nest
104 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 tree
113 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)');
134121
135 // 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 tree
136 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)');
144
145 // Move the fungible token inside token A deeper in the nesting tree
146 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});
155132
156describe('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;
137
157 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 });
164144
165 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});
171149
172 // Create a nested token150 // Create an immediately nested token
173 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());
176154
177 // Create a token to be nested and nest155 // Create a token to be nested and nest
178 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 });
184161
185 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});
191166
192 // Create a nested token by an administrator167 // Create an immediately nested token by an administrator
193 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());
196171
197 // Create a token and allow the owner to nest too172 // Create a token to be nested and nest
198 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 });
204178
205 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});
213186
214 // Create a nested token187 // Create an immediately nested token
215 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());
218191
219 // Create a token to be nested and nest192 // Create a token to be nested and nest
220 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 });
226198
227 // ---------- Non-Fungible ----------199 // ---------- Non-Fungible ----------
228200
229 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());
234206
235 // Create a nested token207 // Create an immediately nested token
236 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());
239211
240 // Create a token to be nested and nest212 // Create a token to be nested and nest
241 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 });
247218
248 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});
253224
254 // 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());
258228
259 // 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());
232
261 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});233 // Create an immediately nested token
234 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());
237
238 // Create a token to be nested and nest
239 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 });
266244
267 // ---------- Fungible ----------245 // ---------- Fungible ----------
268246
269 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});
275251
276 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});252 await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});
253 await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
277254
278 // 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());
284258
285 // Nest a new token259 // Create an immediately nested token
286 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);
262
287 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');263 // Create a token to be nested and nest
264 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 });
290268
291 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)};
296273
297 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());
298277
299 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());
300281
301 // Create a nested token282 // Create an immediately nested token
302 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;
307285
308 // Nest a new token286 // Create a token to be nested and nest
309 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 });
313291
314 // ---------- Re-Fungible ----------292 // ---------- Re-Fungible ----------
315293
316 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});
318298
319 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());
324301
325 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());
326305
327 // Create a nested token306 // Create an immediately nested token
328 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;
333309
334 // Nest a new token310 // Create a token to be nested and nest
335 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 });
339315
340 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});
342320
343 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());
347324
348 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());
349328
350 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});329 // Create an immediately nested token
330 const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount());
331 expect(await nestedToken.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
351332
352 // Create a nested token333 // Create a token to be nested and nest
353 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
354 collectionRFT,
355 targetAddress,
356 {ReFungible: {pieces: 100}},
357 ))).to.not.be.rejected;
358
359 // Nest a new token
360 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});
365339
366describe('Negative Test: Nesting', async() => {340describe('Negative Test: Nesting', () => {
341 let alice: IKeyringPair;
342 let bob: IKeyringPair;
343
367 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 });
373350
374 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);
379354
380 const maxNestingLevel = 5;355 const maxNestingLevel = 5;
381 let prevToken = targetToken;
382356
383 // Create a nested-token matryoshka357 // Create a nested-token matryoshka
384 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 }
391361
392 prevToken = nestedToken;362 // The nesting depth is limited by `maxNestingLevel`
393 }
394
395 // 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$/);
401
402 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 });
405368
406 // ---------- Admin ------------369 // ---------- Admin ------------
407370
408 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);
414375
415 // 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 disallowed
416 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/);
421379
422 // Try to create and nest a token in the wrong collection380 // Try to create a token to be nested and nest
423 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/);
384
429 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 });
432388
433 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');
441394
442 // 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 disallowed
443 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/);
448398
449 // Try to create and nest a token in the wrong collection399 // Try to create a token to be nested and nest
450 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/);
403
455 ), '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 });
459407
460 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());
465414
466 await addToAllowListExpectSuccess(alice, collection, bob.address);415 // Try to nest somebody else's token
416 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/);
469419
470 // Create a token to attempt to be nested into420 // Try to unnest a token belonging to someone else as collection admin
471 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/);
473424
474 // 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});
482
483 // 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 });
494429
495 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);
500435
501 // Create a token to attempt to be nested into436 // Try to create a nested token from another collection
502 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');437 await expect(collectionB.mintToken(alice, targetToken.nestingAccount()))
438 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
503439
504 // Try to create and nest a token in the wrong collection440 // Create a token in another collection yet to be nested and try to nest
505 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/);
444
511 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 });
514448
515 // ---------- Non-Fungible ----------449 // ---------- Non-Fungible ----------
516450
517 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 creation
519 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);
522455
523 // Try to create a nested token456 // Try to create a nested token as token owner when it's disallowed
524 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$/);
529459
530 // Create a token to be nested460 // Try to create a token to be nested and nest
531 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/);
464
534 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 });
538468
539 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);
543472
544 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());
547476
548 // Create a token to attempt to be nested into477 // Try to create a token to be nested and nest
549 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/);
550480
551 // 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/);
557
558 // 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 });
564484
565 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);
569488
570 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());
573492
574 // 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());
576496
577 // Try to create a nested token in the wrong collection497 // Try to create a token to be nested and nest
578 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/);
583500
584 // 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 });
590504
591 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 allowed
593 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});
595509
596 // 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());
598512
599 // Try to create a nested token in the wrong collection513 // Try to mint in own collection after allowlisting the accounts
600 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/);
605
606 // Try to create and nest a token in the wrong collection
607 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 });
612517
613 // ---------- Fungible ----------518 // ---------- Fungible ----------
614519
615 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);
621524
622 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});525 // Try to create an immediately nested token
526 await expect(collectionFT.mint(alice, 5n, targetToken.nestingAccount()))
527 .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
623528
624 // Try to create a nested token529 // Try to create a token to be nested and nest
625 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$/);
630
631 // Create a token to be nested
632 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/);
635
636 // 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 token
639 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 });
642535
643 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});
647540
648 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);541 // Nest some tokens as Alice into Bob's token
649 await enableAllowListExpectSuccess(alice, collectionNFT);
650 await enablePublicMintingExpectSuccess(alice, collectionNFT);542 await collectionFT.mint(alice, 5n, targetToken.nestingAccount());
651543
652 // Create a token to attempt to be nested into544 // Try to pull it out
653 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');
654 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};
655
656 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
657
658 // Try to create a nested token in the wrong collection
659 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/);
664
665 // 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 });
670549
671 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});
677554
678 // 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)};
681556
682 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});557 // Nest some tokens as Alice into Bob's token
683 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}});558 await collectionFT.mint(alice, 5n, targetToken.nestingAccount());
684559
685 // Try to create a nested token in the wrong collection560 // Try to pull it out as Alice still
686 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/);
691
692 // 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 });
697565
698 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);
702570
703 // Create a token to attempt to be nested into571 // Try to mint an immediately nested token
704 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/);
706574
707 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});575 // Mint a token and try to nest it
576 await collectionFT.mint(alice, 5n);
577 await expect(collectionFT.transfer(alice, targetToken.nestingAccount(), 1n))
578 .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
708579
709 // 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/);
715
716 // 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 });
721583
722 // ---------- Re-Fungible ----------584 // ---------- Re-Fungible ----------
723585
724 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);
726590
727 await usingApi(async api => {591 // Try to create an immediately nested token
728 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/);
732594
733 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});595 // Try to create a token to be nested and nest
734
735 // Create a nested token
736 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$/);
741
742 // Create a token to be nested
743 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');596 const token = await collectionRFT.mintToken(alice, 5n);
744 // Try to nest
745 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/);
748
749 // 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 token
752 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 });
755601
756 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);
758606
759 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());
762610
763 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);611 // Try to create a token to be nested and nest
764 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/);
766614
767 // 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);
770617
771 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});618 // Nest some tokens as Alice into Bob's token
619 await newToken.transfer(alice, targetToken.nestingAccount());
772620
773 // Try to create a nested token in the wrong collection621 // Try to pull it out
774 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/);
779
780 // 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 });
785626
786 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);
788631
789 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);
794635
795 // Create a token to attempt to be nested into636 // Try to create a token to be nested and nest
796 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/);
798639
799 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);
801642
802 // Try to create a nested token in the wrong collection643 // Nest some tokens as Alice into Bob's token
803 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/);
808645
809 // Try to create and nest a token in the wrong collection646 // Try to pull it out
810 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 });
814651
815 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);
817656
818 await usingApi(async api => {657 // Try to create an immediately nested token
819 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/);
821660
822 // Create a token to attempt to be nested into661 // Try to create a token to be nested and nest
823 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))
825
826 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
827
828 // Try to create a nested token in the wrong collection
829 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/);
834
835 // 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});
841668
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
1import {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';
163
17let alice: IKeyringPair;4// Unique Network is free software: you can redistribute it and/or modify
18let bob: IKeyringPair;5// it under the terms of the GNU General Public License as published by
19let charlie: IKeyringPair;6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
208
21describe('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 of
24 alice = privateKeyWrapper('//Alice');11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 bob = privateKeyWrapper('//Bob');12// GNU General Public License for more details.
26 });
27 });
2813
29 async function testMakeSureSuppliesRequired(mode: CollectionMode) {14// You should have received a copy of the GNU General Public License
30 await usingApi(async api => {
31 const collectionId = await createCollectionExpectSuccess({mode: mode});15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
3216
33 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;
3820
39 const propertyPermissions = [21// ---------- COLLECTION PROPERTIES
40 {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},
41 {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},
42 ];
43 await expect(executeTransaction(
44 api,
45 alice,
46 api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions),
47 )).to.not.be.rejected;
4822
49 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;
5826
59 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 });
6433
65 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 });
6838
69 it('Makes sure collectionById supplies required fields for ReFungible', async function() {39 async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {
40 // As owner
70 await requirePallets(this, [Pallets.ReFungible]);41 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;
7142
72 await testMakeSureSuppliesRequired({type: 'ReFungible'});43 await collection.addAdmin(alice, {Substrate: bob.address});
73 });
74});
7544
76// ---------- COLLECTION PROPERTIES45 // As administrator
46 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;
7747
78describe('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 }
54
55 itSub('Sets properties for a NFT collection', async ({helper}) => {
83 });56 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));
84 });57 });
8558
86 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 });
9462
63 async function testCheckValidNames(collection: UniqueBaseCollection) {
64 // alpha symbols
65 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;
9566
96 async function testSetsPropertiesForCollection(mode: string) {67 // numeric symbols
97 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);
10069
101 // As owner70 // underscore symbol
102 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;
10772
108 await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);73 // dash symbol
74 await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;
10975
110 // As administrator76 // dot symbol
111 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;
11678
117 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]);
12988
130 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 });
13292
133 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 symbols
139 await expect(executeTransaction(
140 api,
141 bob,
142 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]),
143 )).to.not.be.rejected;
144
145 // numeric symbols
146 await expect(executeTransaction(
147 api,
148 bob,
149 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]),
150 )).to.not.be.rejected;
151
152 // underscore symbol
153 await expect(executeTransaction(
154 api,
155 bob,
156 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]),
157 )).to.not.be.rejected;
158
159 // dash symbol
160 await expect(executeTransaction(
161 api,
162 bob,
163 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]),
164 )).to.not.be.rejected;
165
166 // underscore symbol
167 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]);
18996
190 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;
19299
193 async function testChangesProperties(mode: CollectionMode) {100 // Mutate the properties
194 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 properties
204 await expect(executeTransaction(101 await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;
205 api, 102
206 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 }
109
217 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]);
222113
223 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 });
225117
226 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});120
229
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, 122
238 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 }
128
248 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]);
253132
254 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});
257137
258describe('Negative Integration Test: Collection Properties', () => {138describe('Negative Integration Test: Collection Properties', () => {
139 let alice: IKeyringPair;
140 let bob: IKeyringPair;
141
259 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 152
276 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 }
155
281 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]);
286159
287 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 process
296 {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 174
305 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;176
307
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/);181
316
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 }
184
321 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]);
326188
327 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 201
342 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 204
348 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 }
207
353 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]);
358211
359 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 222
372 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 229
380 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/);
234
386 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;
239
394 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 241
396 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 246
401 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 }
254
410 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]);
415258
416 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});
419263
420// ---------- ACCESS RIGHTS264// ---------- ACCESS RIGHTS
421265
422describe('Integration Test: Access Rights to Token Properties', () => {266describe('Integration Test: Access Rights to Token Properties', () => {
267 let alice: IKeyringPair;
268 let bob: IKeyringPair;
269
423 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 286
448 await addCollectionAdminExpectSuccess(alice, collection, bob.address);287 await collection.addAdmin(alice, {Substrate: bob.address});
449 288
450 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 291
456 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 }
298
463 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]);
468302
469 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 310
482 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 313
488 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 }
319
494 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]);
499323
500 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});
503328
504describe('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;
332
505 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 });
511339
512 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 343
522 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 }
347
526 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]);
531351
532 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 });
534355
535 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 364
547 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 367
553 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 }
371
557 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]);
562375
563 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 });
565379
566 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 383
576 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 386
582 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 }
392
588 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]);
593396
594 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 });
596400
597 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 407
607 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 414
615 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/);
419
621 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-string
622 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;
427
630 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 429
632 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 }
435
638 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]);
643439
644 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});
647444
648// ---------- TOKEN PROPERTIES445// ---------- TOKEN PROPERTIES
649446
650describe('Integration Test: Token Properties', () => {447describe('Integration Test: Token Properties', () => {
448 let alice: IKeyringPair; // collection owner
449 let bob: IKeyringPair; // collection admin
450 let charlie: IKeyringPair; // token owner
451
651 let permissions: {permission: any, signers: IKeyringPair[]}[];452 let permissions: {permission: any, signers: IKeyringPair[]}[];
652453
653 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 owner
658
659 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 });
459
460 // todo:playgrounds probably separate these tests later
461 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);474
678 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 }
478
683 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]);
688484
689 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 });
691490
692 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);
698494
699 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);
705504
706 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;
711509
712 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;
718
719 i++;
720 }514 }
515 }
721516
722 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 }
524
730 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]);
735530
736 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 });
738536
739 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);
745540
746 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 552
755 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;
557
761 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;
562
767 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 }
569
777 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 }
577
785 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]);
790583
791 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 });
793589
794 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);
800593
801 const propertyKeys: string[] = [];594 const propertyKeys: string[] = [];
802 let i = 0;595 let i = 0;
803 596
804 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 606
811 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;
611
817 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;
616
823 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[];623
834 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]);
845633
846 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 });
848639
849 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);
857645
858 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);
864648
865 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;
870663
871 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;
877
878 i++;
879 }668 }
880669
881 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];670 }
671
672 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 });
889680
890 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);
898686
899 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);
907689
908 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);
913701
914 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;
919706
920 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;
926711
927 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 }
929718
930 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 });
938727
939 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);
947733
948 const propertyKeys: string[] = [];734 await collectionB.addAdmin(alice, {Substrate: bob.address});
949 let i = 0;735 await targetToken.transfer(alice, {Substrate: charlie.address});
950736
951 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);
957748
958 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;
963753
964 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;
969758
970 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 }
979765
980 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});
988771
989describe('Negative Integration Test: Token Properties', () => {772describe('Negative Integration Test: Token Properties', () => {
990 let collection: number;773 let alice: IKeyringPair; // collection owner
991 let token: number;774 let bob: IKeyringPair; // collection admin
992 let originalSpace: number;775 let charlie: IKeyringPair; // token owner
776
993 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];777 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];
994778
995 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');
1001784
785 // todo:playgrounds probably separate these tests later
1002 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 });
1012796
1013 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 }
1042800
1043 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 => {804
1047 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;
814
1058 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 820
1065 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 }
824
1069 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);
827
828 let i = 0;
829 for (const forbiddance of constitution) {
830 i++;
831 if (!forbiddance.permission.mutable) continue;
832
833 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/);
837
838 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 }
843
844 const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT');
845 expect(consumedSpace).to.be.equal(originalSpace);
846 }
847
848 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]);
1074853
1075 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 });
1077859
1078 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);
862
1080 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;
867
1083 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/);
872
1085 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 }
882
1104 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]);
1109888
1110 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 });
1112894
1113 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);
1115897
1116 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;
907
1129 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/);
912
1135 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 }
918
1140 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]);
1145924
1146 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 });
1148930
1149 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);
1151933
1152 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;
941
942 // Mute the general tx parsing error
943 {
944 console.error = () => {};
945 await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))
946 .to.be.rejected;
947 }
948
949 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 }
958
1186 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]);
1191964
1192 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});
1195971
1196describe('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;
1199976
1200 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]);
1202980
1203 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 });
1209985
1210 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 }
1215995
1216 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);
998
1219 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 999 await token.transfer(alice, {Substrate: charlie.address}, 33n);
1000
1001 await expect(token.setProperties(alice, [
1002 {key: 'fractals', value: 'multiverse'},
1220 )).to.not.be.rejected;1003 ])).to.be.rejectedWith(/common\.NoPermission/);
1221 });
1222 });1004 });
12231005
1224 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);
1008
1226 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;
1011
1228 await expect(executeTransaction(1012 await expect(token.setProperties(alice, [
1229 api, 1013 {key: 'fractals', value: 'multiverse'},
1230 alice, 1014 ])).to.be.fulfilled;
1015
1231 api.tx.unique.setTokenProperties(collection, token, [1016 await token.transfer(alice, {Substrate: charlie.address}, 33n);
1017
1018 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 });
12371022
1238 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;
12531025
1254 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;
1029
1260 {key: 'key', value: 'bad word'}, 1030 await token.transfer(alice, {Substrate: charlie.address}, 33n);
1031
1261 ]), 1032 await expect(token.deleteProperties(alice, ['fractals'])).
1262 )).to.be.rejectedWith(/common\.NoPermission/);1033 to.be.rejectedWith(/common\.NoPermission/);
1263 });
1264 });1034 });
12651035
1266 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;
12751038
1276 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1039 await token.transfer(alice, {Substrate: charlie.address}, 33n);
1277 1040
1278 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;
1043
1281 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});
12881049
deletedtests/src/nesting/rules-smoke.test.tsdiffbeforeafterboth

no changes

modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
2// This file is part of Unique Network.
3
4// Unique Network is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Unique Network is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
16
2import {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';
17
18let alice: IKeyringPair;
19let bob: IKeyringPair;
2019
21describe('Integration Test: Unnesting', () => {20describe('Integration Test: Unnesting', () => {
21 let alice: IKeyringPair;
22
22 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 });
2829
29 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)};
35
36 // Create a nested token34 // Create a nested token
37 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);35 const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());
3836
39 // Unnest37 // Unnest
40 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});
4640
47 // Nest and burn41 // Nest and burn
48 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 });
5746
58 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);
50
51 const collectionFT = await helper.ft.mintCollection(alice);
52
53 // Nest and unnest
60 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)};
64
65 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');
67
68 // Nest and unnest
69 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');
7158
72 // Nest and burn59 // Nest and burn
73 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 });
8466
85 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]);
87
88 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)};
9370
94 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});71 const collectionRFT = await helper.rft.mintCollection(alice);
72
73 // Nest and unnest
95 const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');74 const token = await collectionRFT.mintToken(alice, 10n, targetToken.nestingAccount());
96
97 // Nest and unnest
98 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);
10078
101 // Nest and burn79 // Nest and burn
102 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});
11387
114describe('Negative Test: Unnesting', () => {88describe('Negative Test: Unnesting', () => {
89 let alice: IKeyringPair;
90 let bob: IKeyringPair;
91
115 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 });
12198
122 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)};
128102
129 // Create a nested token103 // Create a nested token
130 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);104 const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());
131105
132 // Try to unnest106 // Try to unnest
133 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());
139109
140 // Try to burn110 // Try to burn
141 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 });
149114
150 // todo another test for creating excessive depth matryoshka with Ethereum?115 // todo another test for creating excessive depth matryoshka with Ethereum?
151116
152 // Recursive nesting117 // Recursive nesting
153 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);
157121
158 // Create a nested token ouroboros122 // Fail to create a nested token ouroboros
159 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});
163127
modifiedtests/src/util/playgrounds/index.tsdiffbeforeafterboth

no syntactic changes

modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
9494
95export interface IProperty {95export interface IProperty {
96 key: string;96 key: string;
97 value: string;97 value?: string;
98}98}
9999
100export 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}
108108
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
55 RPC: 'rpc',55 RPC: 'rpc',
56 };56 };
5757
58 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 }
6161
62 static getTokenAccountInLowerCase(token: IToken): ICrossAccountId {
63 return {Ethereum: this.getTokenAddress(token).toLowerCase()};
64 }
65
66 static getTokenAddress(token: IToken): string {
67 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);
68 }
69
62 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();
593601
594 return normalize602 return normalize
595 ? admins.map((address: any) => {603 ? admins.map((address: any) => this.helper.address.normalizeCrossAccountIfSubstrate(address))
596 return address.Substrate
597 ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)}
598 : address;
599 })
600 : admins;604 : admins;
601 }605 }
602606
610 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 normalize
613 ? allowListed.map((address: any) => {617 ? allowListed.map((address: any) => this.helper.address.normalizeCrossAccountIfSubstrate(address))
614 return address.Substrate
615 ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)}
616 : address;
617 })
618 : allowListed;618 : allowListed;
619 }619 }
620620
897 }897 }
898898
899 /**899 /**
900 * Get collection properties.
901 *
902 * @param collectionId ID of collection
903 * @param propertyKeys optionally filter the returned properties to only these keys
904 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);
905 * @returns array of key-value pairs
906 */
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 }
910
911 /**
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 signer
988 *1000 *
989 * @param signer keyring of signer1001 * @param signer keyring of signer
990 * @param collectionId ID of collection1002 * @param collectionId ID of collection
1003 * @param tokenId ID of token
991 * @param fromAddressObj address on behalf of which the token will be burnt1004 * @param fromAddressObj address on behalf of which the token will be burnt
992 * @param tokenId ID of token
993 * @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 1n
994 * @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 signer
1119 * @param collectionId ID of collection1131 * @param collectionId ID of collection
1120 * @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 owner
1121 * @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 }
11351147
1136 /**1148 /**
1149 * Get token property permissions.
1150 *
1151 * @param collectionId ID of collection
1152 * @param propertyKeys optionally filter the returned property permissions to only these keys
1153 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);
1154 * @returns array of key-permission pairs
1155 */
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 }
1159
1160 /**
1137 * Set token properties1161 * Set token properties
1138 *1162 *
1139 * @param signer keyring of signer1163 * @param signer keyring of signer
1154 }1178 }
11551179
1156 /**1180 /**
1181 * Get properties, metadata assigned to a token.
1182 *
1183 * @param collectionId ID of collection
1184 * @param tokenId ID of token
1185 * @param propertyKeys optionally filter the returned properties to only these keys
1186 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);
1187 * @returns array of key-value pairs
1188 */
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 }
1192
1193 /**
1157 * Delete the provided properties of a token1194 * Delete the provided properties of a token
1158 * @param signer keyring of signer1195 * @param signer keyring of signer
1159 * @param collectionId ID of collection1196 * @param collectionId ID of collection
1181 * @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 collection
1183 */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 object
1186 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 UniqueNFTToken
1225 */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 }
12291266
1230 /**1267 /**
13041341
1305 if (owner === null) return null;1342 if (owner === null) return null;
13061343
1307 owner = owner.toHuman();1344 return owner.toHuman();
1308
1309 return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;
1310 }1345 }
13111346
1312 /**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 collection
1379 */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 }
13831418
1387 * @param data token data1422 * @param data token data
1388 * @returns created token object1423 * @returns created token object
1389 */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 tokens
1448 */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 UniqueNFTToken
1497 */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 }
15011536
1502 /**1537 /**
1563 * })1598 * })
1564 * @returns object of the created collection1599 * @returns object of the created collection
1565 */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 }
15691604
1574 * @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 object
1576 */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 }
15931628
1594 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 tokens
1613 */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 token
1634 * @param amount number of pieces to be burnt1669 * @param amount number of pieces to be burnt
1635 * @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 }
16411676
1642 /**1677 /**
1678 * Destroys a concrete instance of RFT on behalf of the owner.
1679 * @param signer keyring of signer
1680 * @param collectionId ID of collection
1681 * @param tokenId ID of token
1682 * @param fromAddressObj address on behalf of which the token will be burnt
1683 * @param amount number of pieces to be burnt
1684 * @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 }
1690
1691 /**
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 signer
1711 * }, 18)1760 * }, 18)
1712 * @returns newly created fungible collection1761 * @returns newly created fungible collection
1713 */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 object
1716 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 }
18451894
1846 /**1895 /**
19361985
1937class 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 }
20182067
2019 /**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 changed
2071 * @param ss58Format format for address conversion, by default ```42```
2072 * @example normalizeCrossAccountIfSubstrate({Substrate: "unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx"}) // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
2073 * @returns untouched ethereum account or substrate account converted to normalized (i.e., starting with 5) or specified explicitly representation
2074 */
2075 normalizeCrossAccountIfSubstrate(account: ICrossAccountId, ss58Format = 42): ICrossAccountId {
2076 return account.Substrate
2077 ? {Substrate: this.normalizeSubstrate(account.Substrate, ss58Format)}
2078 : account;
2079 }
2080
2081 /**
2020 * Get address in the connected chain format2082 * Get address in the connected chain format
2021 * @param address substrate address2083 * @param address substrate address
2022 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2084 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network
2161}2223}
21622224
21632225
2164class UniqueCollectionBase {2226export class UniqueBaseCollection {
2165 helper: UniqueHelper;2227 helper: UniqueHelper;
2166 collectionId: number;2228 collectionId: number;
21672229
2194 return await this.helper.collection.getEffectiveLimits(this.collectionId);2256 return await this.helper.collection.getEffectiveLimits(this.collectionId);
2195 }2257 }
21962258
2259 async getProperties(propertyKeys: string[] | null = null) {
2260 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);
2261 }
2262
2263 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
2264 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
2265 }
2266
2197 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 }
22402310
2241 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
2242 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
2243 }
2244
2245 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}
22612327
22622328
2263class 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 }
22672333
2268 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 }
22872353
2354 async getPropertyPermissions(propertyKeys: string[] | null = null) {
2355 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);
2356 }
2357
2358 async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) {
2359 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);
2360 }
2361
2288 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 }
23152389
2390 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {
2391 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);
2392 }
2393
2316 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}
23362414
23372415
2338class 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 }
23422420
2421 async getToken(tokenId: number, blockHashAt?: string) {
2422 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);
2423 }
2424
2343 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 }
23582440
2441 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
2442 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);
2443 }
2444
2445 async getPropertyPermissions(propertyKeys: string[] | null = null) {
2446 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);
2447 }
2448
2449 async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) {
2450 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);
2451 }
2452
2359 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 }
23702464
2371 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
2372 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);
2373 }
2374
2375 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 }
23902480
2481 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {
2482 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);
2483 }
2484
2391 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}
24032497
24042498
2405class 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 }
24092503
2410 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 }
24132507
2414 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 }
24172511
2418 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 }
24212515
2516 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {
2517 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);
2518 }
2519
2520 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 }
2523
2422 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 }
24372539
2438 async getTotalPieces() {
2439 return await this.helper.ft.getTotalPieces(this.collectionId);
2440 }
2441
2442 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 }
2445
2446 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
2447 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);
2448 }
2449}2543}
24502544
24512545
2452class 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 }
24662560
2561 async getProperties(propertyKeys: string[] | null = null) {
2562 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);
2563 }
2564
2467 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 }
24702568
2471 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 }
2572
2573 nestingAccount() {
2574 return this.collection.helper.util.getTokenAccount(this);
2575 }
2576
2577 nestingAccountInLowerCase() {
2578 return this.collection.helper.util.getTokenAccountInLowerCase(this);
2579 }
2474}2580}
24752581
24762582
2477class UniqueNFTToken extends UniqueTokenBase {2583export class UniqueNFToken extends UniqueBaseToken {
2478 collection: UniqueNFTCollection;2584 collection: UniqueNFTCollection;
24792585
2480 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 }
2634
2635 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {
2636 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);
2637 }
2528}2638}
25292639
2530class UniqueRFTToken extends UniqueTokenBase {2640export class UniqueRFToken extends UniqueBaseToken {
2531 collection: UniqueRFTCollection;2641 collection: UniqueRFTCollection;
25322642
2533 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 }
25372647
2648 async getData(blockHashAt?: string) {
2649 return await this.collection.getToken(this.tokenId, blockHashAt);
2650 }
2651
2538 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 }
25492663
2664 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {
2665 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);
2666 }
2667
2550 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 }
25612679
2562 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {
2563 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);
2564 }
2565
2566 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 }
25692683
2570 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 }
2687
2688 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {
2689 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);
2572 }2690 }
2573}2691}
25742692