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

difftreelog

test(structure) refactor properties and nesting

Fahrrader2022-05-13parent: #1c4a2b3.patch.diff
in: master

7 files changed

modifiedtests/package.jsondiffbeforeafterboth
33 "loadTransfer": "ts-node src/transfer.nload.ts",33 "loadTransfer": "ts-node src/transfer.nload.ts",
34 "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",34 "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",
35 "testEvent": "mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",35 "testEvent": "mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",
36 "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",36 "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",
37 "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",
38 "testStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",
37 "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts",39 "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts",
38 "testMigrationStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",40 "testMigrationStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",
39 "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",41 "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
modifiedtests/src/nesting/graphs.test.tsdiffbeforeafterboth
14 * ```14 * ```
15 */15 */
16async function buildComplexObjectGraph(api: ApiPromise, sender: IKeyringPair): Promise<number> {16async function buildComplexObjectGraph(api: ApiPromise, sender: IKeyringPair): Promise<number> {
17 const events = await executeTransaction(api, sender, api.tx.unique.createCollectionEx({mode: 'NFT'}));17 const events = await executeTransaction(api, sender, api.tx.unique.createCollectionEx({mode: 'NFT', limits: {nestingRule: 'Owner'}}));
18 const {collectionId} = getCreateCollectionResult(events);18 const {collectionId} = getCreateCollectionResult(events);
1919
20 await executeTransaction(api, sender, api.tx.unique.createMultipleItemsEx(collectionId, {NFT: Array(8).fill({owner: {Substrate: sender.address}})}));20 await executeTransaction(api, sender, api.tx.unique.createMultipleItemsEx(collectionId, {NFT: Array(8).fill({owner: {Substrate: sender.address}})}));
32 return collectionId;32 return collectionId;
33}33}
3434
35describe('graphs', () => {35describe('Graphs', () => {
36 it('ouroboros can\'t be created in graph', async () => {36 it('Ouroboros can\'t be created in a complex graph', async () => {
37 await usingApi(async api => {37 await usingApi(async api => {
38 const alice = privateKey('//Alice');38 const alice = privateKey('//Alice');
39 const collection = await buildComplexObjectGraph(api, alice);39 const collection = await buildComplexObjectGraph(api, alice);
4040
41 // to self41 // to self
42 await expect(executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 1), collection, 1, 1)))42 await expect(
43 executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 1), collection, 1, 1)),
44 'first transaction',
43 .to.be.rejectedWith(/structure\.OuroborosDetected/);45 ).to.be.rejectedWith(/structure\.OuroborosDetected/);
44 // to nested part of graph46 // to nested part of graph
45 await expect(executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 5), collection, 1, 1)))47 await expect(
48 executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 5), collection, 1, 1)),
49 'second transaction',
46 .to.be.rejectedWith(/structure\.OuroborosDetected/);50 ).to.be.rejectedWith(/structure\.OuroborosDetected/);
47 await expect(executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 8), collection, 2, 1)))51 await expect(
52 executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 8), collection, 2, 1)),
53 'third transaction',
48 .to.be.rejectedWith(/structure\.OuroborosDetected/);54 ).to.be.rejectedWith(/structure\.OuroborosDetected/);
49 });55 });
50 });56 });
modifiedtests/src/nesting/migration-check.test.tsdiffbeforeafterboth
18 });18 });
19 });19 });
2020
21 it('Preserves collection settings', async () => {21 it('Preserves collection settings after migration', async () => {
22 let oldVersion: number;22 let oldVersion: number;
23 let collectionId: number;23 let collectionId: number;
24 let collectionOld: any;24 let collectionOld: any;
71 });71 });
72 } catch (_) {72 } catch (_) {
73 connectionFailCounter++;73 connectionFailCounter++;
74 console.log(`Still waiting for the parachain upgrade from ${oldVersion!}...`);
74 await new Promise(resolve => setTimeout(resolve, 12000));75 await new Promise(resolve => setTimeout(resolve, 12000));
75 }76 }
76 }77 }
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
5import {5import {
6 addToAllowListExpectSuccess,6 addToAllowListExpectSuccess,
7 createCollectionExpectSuccess,7 createCollectionExpectSuccess,
8 createItemExpectFailure,
9 createItemExpectSuccess,8 createItemExpectSuccess,
10 enableAllowListExpectSuccess,9 enableAllowListExpectSuccess,
11 enablePublicMintingExpectSuccess,10 enablePublicMintingExpectSuccess,
12 getTokenOwner, 11 getTokenOwner,
13 getTopmostTokenOwner, 12 getTopmostTokenOwner,
13 normalizeAccountId,
14 setCollectionLimitsExpectSuccess, 14 setCollectionLimitsExpectSuccess,
15 transferExpectFailure, 15 transferExpectFailure,
16 transferExpectSuccess, 16 transferExpectSuccess,
29 });29 });
30 });30 });
3131
32 // ---------- Non-Fungible ----------
33
34 // todo refactor names
35 // todo remove excessive bundling, leave it for a single test
36 it('NFT: allows to nest/unnest token if nesting rule is Owner', async () => {32 it('Performs the full suite: bundles a token, transfers, and allows to unnest', async () => {
37 await usingApi(async api => {33 await usingApi(async api => {
38 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});34 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
39 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});35 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
49 45
50 // Nest46 // Nest
51 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});47 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
52 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});48 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
53 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});49 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
5450
55 // Move bundle to different user51 // Move bundle to different user
56 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});52 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});
63 });59 });
64 });60 });
61
62 // ---------- Non-Fungible ----------
63
64 it('NFT: allows an Owner to nest/unnest their token', async () => {
65 await usingApi(async api => {
66 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
67 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
68 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
69
70 // Create a nested token
71 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
72 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
73 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
74
75 // Create a token to be nested and nest
76 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
77 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
78 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
79 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
80 });
81 });
6582
66 it('NFT: allows to nest/unnest token if Owner-Restricted', async () => {83 it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {
67 await usingApi(async api => {84 await usingApi(async api => {
68 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});85 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
69 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});86 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});
74 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});91 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
75 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});92 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
7693
77 // Create a token to be nested94 // Create a token to be nested and nest
78 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');95 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
79
80 // Nest
81 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});96 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});
82 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});97 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
83 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});98 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
84
85 // Move bundle to different user
86 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});
87 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});
88 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
89
90 // Unnest
91 await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});
92 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});
93 });99 });
94 });100 });
95101
96 // ---------- Fungible ----------102 // ---------- Fungible ----------
97103
98 it('Fungible: allows to nest/unnest token if Owner', async () => {104 it('Fungible: allows an Owner to nest/unnest their token', async () => {
99 await usingApi(async api => {105 await usingApi(async api => {
100 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});106 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
101 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});107 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
111 {Fungible: {Value: 10}},117 {Fungible: {Value: 10}},
112 ))).to.not.be.rejected;118 ))).to.not.be.rejected;
113119
114 // Create a token to be nested120 // Nest a new token
115 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');121 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
116 // Nest
117 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');122 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');
118 // Move bundle to different user
119 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
120 // Unnest
121 await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');
122 });123 });
123 });124 });
124125
125 it('Fungible: allows to nest/unnest token if Owner-Restricted', async () => {126 it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {
126 await usingApi(async api => {127 await usingApi(async api => {
127 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});128 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
128 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});129 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
139 {Fungible: {Value: 10}},140 {Fungible: {Value: 10}},
140 ))).to.not.be.rejected;141 ))).to.not.be.rejected;
141142
142 // Create a token to be nested143 // Nest a new token
143 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');144 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
144 // Nest
145 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');145 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');
146 // Move bundle to different user
147 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
148 // Unnest
149 await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');
150 });146 });
151 });147 });
152148
153 // ---------- Re-Fungible ----------149 // ---------- Re-Fungible ----------
154150
155 it('ReFungible: allows to nest/unnest token if Owner', async () => {151 it('ReFungible: allows an Owner to nest/unnest their token', async () => {
156 await usingApi(async api => {152 await usingApi(async api => {
157 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});153 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
158 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});154 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
165 await expect(executeTransaction(api, alice, api.tx.unique.createItem(161 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
166 collectionRFT, 162 collectionRFT,
167 targetAddress, 163 targetAddress,
168 {ReFungible: {const_data: [], variable_data: [], pieces: 100}},164 {ReFungible: {const_data: [], pieces: 100}},
169 ))).to.not.be.rejected;165 ))).to.not.be.rejected;
170166
171 // Create a token to be nested167 // Nest a new token
172 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');168 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
173 // Nest
174 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');169 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');
175 // Move bundle to different user
176 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
177 // Unnest
178 await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');
179 });170 });
180 });171 });
181172
182 it('ReFungible: allows to nest/unnest token if Owner-Restricted', async () => {173 it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {
183 await usingApi(async api => {174 await usingApi(async api => {
184 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});175 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
185 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});176 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
193 await expect(executeTransaction(api, alice, api.tx.unique.createItem(184 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
194 collectionRFT, 185 collectionRFT,
195 targetAddress,186 targetAddress,
196 {ReFungible: {const_data: [], variable_data: [], pieces: 100}},187 {ReFungible: {const_data: [], pieces: 100}},
197 ))).to.not.be.rejected;188 ))).to.not.be.rejected;
198189
199 // Create a token to be nested190 // Nest a new token
200 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');191 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
201 // Nest
202 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');192 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');
203 // Move bundle to different user
204 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});
205 // Unnest
206 await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');
207 });193 });
208 });194 });
209});195});
216 });202 });
217 });203 });
204
205 it('Disallows excessive token nesting', async () => {
206 await usingApi(async api => {
207 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
208 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
209 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
210
211 // Create a nested-token matryoshka
212 const nestedToken1 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
213 const nestedToken2 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken1)});
214 // The nesting depth is limited by 2
215 await expect(executeTransaction(
216 api, alice,
217 api.tx.unique.createItem(
218 collection,
219 {Ethereum: tokenIdToAddress(collection, nestedToken2)},
220 {nft: {const_data: [], variable_data: []}} as any,
221 ),
222 ), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/); // OuroborosDetected?
223
224 expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});
225 });
226 });
218227
219 // ---------- Non-Fungible ----------228 // ---------- Non-Fungible ----------
220229
230 {Ethereum: tokenIdToAddress(collection, targetToken)}, 241 {Ethereum: tokenIdToAddress(collection, targetToken)},
231 {nft: {const_data: [], variable_data: []}} as any,242 {nft: {const_data: [], variable_data: []}} as any,
232 ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);243 ),
244 ), 'while creating nested token').to.be.rejectedWith(/^common\.NestingIsDisabled$/);
233245
234 // Create a token to be nested246 // Create a token to be nested
235 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');247 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
236 // Try to nest248 // Try to nest
237 await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)}); // todo to.be.rejected249 await expect(executeTransaction(
250 api, alice,
251 api.tx.unique.transfer(
252 normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,
253 ),
254 ), 'while nesting new token').to.be.rejectedWith(/common\.NestingIsDisabled/); // todo to.be.rejected for all
238 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});255 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
239 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});256 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
240 });257 });
241 });258 });
242259
243 it('NFT: disallows to nest token if not Owner', async () => {260 it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {
244 await usingApi(async api => {261 await usingApi(async api => {
245 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});262 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
246 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});263 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
253 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');270 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');
254271
255 // Try to create a nested token in the wrong collection272 // Try to create a nested token in the wrong collection
256 await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});273 await expect(executeTransaction(
274 api, alice,
275 api.tx.unique.createItem(
276 collection,
277 {Ethereum: tokenIdToAddress(collection, targetToken)},
278 {nft: {const_data: [], variable_data: []}} as any,
279 ),
280 ), 'while creating nested token').to.be.rejectedWith(/structure\.DepthLimit/); // todo NestingIsDisabled?
257281
258 // Try to create and nest a token in the wrong collection282 // Try to create and nest a token in the wrong collection
259 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');283 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
260 await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});284 await expect(executeTransaction(
285 api, alice,
286 api.tx.unique.transfer(
287 normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,
288 ),
289 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);
261 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});290 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
262 });291 });
263 });292 });
264293
265 it('NFT: disallows to nest token if not Owner (Restricted nesting)', async () => {294 it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {
266 await usingApi(async api => {295 await usingApi(async api => {
267 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});296 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
268 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});297 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[collection]}});
275 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');304 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');
276305
277 // Try to create a nested token in the wrong collection306 // Try to create a nested token in the wrong collection
278 await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});307 await expect(executeTransaction(
308 api, alice,
309 api.tx.unique.createItem(
310 collection,
311 {Ethereum: tokenIdToAddress(collection, targetToken)},
312 {nft: {const_data: [], variable_data: []}} as any,
313 ),
314 ), 'while creating nested token').to.be.rejectedWith(/structure\.DepthLimit/);
279315
280 // Try to create and nest a token in the wrong collection316 // Try to create and nest a token in the wrong collection
281 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');317 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
282 await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});318 await expect(executeTransaction(
319 api, alice,
320 api.tx.unique.transfer(
321 normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,
322 ),
323 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);
283 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});324 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
284 });325 });
285 });326 });
286327
287 it('NFT: disallows to nest token to an unlisted collection', async () => {328 it('NFT: disallows to nest token in an unlisted collection', async () => {
288 await usingApi(async api => {329 await usingApi(async api => {
289 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});330 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
290 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[]}});331 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: {OwnerRestricted:[]}});
293 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');334 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
294335
295 // Try to create a nested token in the wrong collection336 // Try to create a nested token in the wrong collection
296 await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});337 await expect(executeTransaction(
338 api, alice,
339 api.tx.unique.createItem(
340 collection,
341 {Ethereum: tokenIdToAddress(collection, targetToken)},
342 {nft: {const_data: [], variable_data: []}} as any,
343 ),
344 ), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
297345
298 // Try to create and nest a token in the wrong collection346 // Try to create and nest a token in the wrong collection
299 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');347 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');
300 await transferExpectFailure(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});348 await expect(executeTransaction(
349 api, alice,
350 api.tx.unique.transfer(
351 normalizeAccountId({Ethereum: tokenIdToAddress(collection, targetToken)}), collection, newToken, 1,
352 ),
353 ), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
301 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});354 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});
302 });355 });
303 });356 });
319 targetAddress, 374 targetAddress,
320 {Fungible: {Value: 10}},375 {Fungible: {Value: 10}},
321 ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);376 )
377 ), 'while creating nested token').to.be.rejectedWith(/^common\.NestingIsDisabled$/);
322378
323 // Create a token to be nested379 // Create a token to be nested
324 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');380 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
325 // Try to nest381 // Try to nest
326 await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);382 await expect(executeTransaction(
383 api, alice,
384 api.tx.unique.transfer(
385 normalizeAccountId({Ethereum: tokenIdToAddress(collectionFT, targetToken)}), collectionFT, newToken, 1,
386 ),
387 ), 'while nesting new token').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);
327 });388 });
328 });389 });
329390
330 it('Fungible: disallows to nest token if not Owner', async () => {391 it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {
331 await usingApi(async api => {392 await usingApi(async api => {
332 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});393 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
333 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});394 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
347 collectionFT, 410 collectionFT,
348 targetAddress, 411 targetAddress,
349 {Fungible: {Value: 10}},412 {Fungible: {Value: 10}},
350 ))).to.be.rejected;413 )
414 ), 'while creating nested token').to.be.rejectedWith(/structure\.DepthLimit/);
351415
352 // Try to create and nest a token in the wrong collection416 // Try to create and nest a token in the wrong collection
353 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');417 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
354 await transferExpectFailure(collectionFT, newToken, alice, targetAddress, 1);418 await expect(executeTransaction(
419 api, alice,
420 api.tx.unique.transfer(
421 normalizeAccountId({Ethereum: tokenIdToAddress(collectionFT, targetToken)}), collectionFT, newToken, 1,
422 ),
423 ), 'while nesting new token').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);
355 });424 });
356 });425 });
357426
358 it('Fungible: disallows to nest token if not Owner (Restricted nesting)', async () => {427 it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {
359 await usingApi(async api => {428 await usingApi(async api => {
360 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});429 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
361 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}}); // todo clear redundant restrictions430 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}}); // todo clear redundant restrictions?
362431
363 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);432 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);
364 await enableAllowListExpectSuccess(alice, collectionNFT);433 await enableAllowListExpectSuccess(alice, collectionNFT);
383 });452 });
384 });453 });
385454
386 it('Fungible: disallows to nest token to an unlisted collection', async () => {455 it('Fungible: disallows to nest token in an unlisted collection', async () => {
387 await usingApi(async api => {456 await usingApi(async api => {
388 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});457 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
389 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});458 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[]}});
422 await expect(executeTransaction(api, alice, api.tx.unique.createItem(491 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
423 collectionRFT, 492 collectionRFT,
424 targetAddress, 493 targetAddress,
425 {ReFungible: {const_data: [], variable_data: [], pieces: 100}},494 {ReFungible: {const_data: [], pieces: 100}},
426 ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);495 ))).to.be.rejectedWith(/^common\.NestingIsDisabled$/);
427496
428 // Create a token to be nested497 // Create a token to be nested
432 });501 });
433 });502 });
434503
435 it('ReFungible: disallows to nest token if not Owner', async () => {504 it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {
436 await usingApi(async api => {505 await usingApi(async api => {
437 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});506 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
438 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});507 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: 'Owner'});
451 await expect(executeTransaction(api, alice, api.tx.unique.createItem(520 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
452 collectionRFT, 521 collectionRFT,
453 targetAddress, 522 targetAddress,
454 {ReFungible: {const_data: [], variable_data: [], pieces: 100}},523 {ReFungible: {const_data: [], pieces: 100}},
455 ))).to.be.rejected;524 ))).to.be.rejected;
456525
457 // Try to create and nest a token in the wrong collection526 // Try to create and nest a token in the wrong collection
460 });529 });
461 });530 });
462531
463 it('ReFungible: disallows to nest token if not Owner (Restricted nesting)', async () => {532 it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {
464 await usingApi(async api => {533 await usingApi(async api => {
465 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});534 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
466 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}});535 await setCollectionLimitsExpectSuccess(alice, collectionNFT, {nestingRule: {OwnerRestricted:[collectionNFT]}});
479 await expect(executeTransaction(api, alice, api.tx.unique.createItem(548 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
480 collectionRFT, 549 collectionRFT,
481 targetAddress, 550 targetAddress,
482 {ReFungible: {const_data: [], variable_data: [], pieces: 100}},551 {ReFungible: {const_data: [], pieces: 100}},
483 ))).to.be.rejected;552 ))).to.be.rejected;
484553
485 // Try to create and nest a token in the wrong collection554 // Try to create and nest a token in the wrong collection
503 await expect(executeTransaction(api, alice, api.tx.unique.createItem(572 await expect(executeTransaction(api, alice, api.tx.unique.createItem(
504 collectionRFT, 573 collectionRFT,
505 targetAddress, 574 targetAddress,
506 {ReFungible: {const_data: [], variable_data: [], pieces: 100}},575 {ReFungible: {const_data: [], pieces: 100}},
507 ))).to.be.rejected;576 ))).to.be.rejected;
508577
509 // Try to create and nest a token in the wrong collection578 // Try to create and nest a token in the wrong collection
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
51 await expect(executeTransaction(51 await expect(executeTransaction(
52 api, 52 api,
53 alice, 53 alice,
54 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black hole'}]), 54 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]),
55 )).to.not.be.rejected;55 )).to.not.be.rejected;
5656
57 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black hole'])).toJSON();57 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();
58 expect(properties).to.be.deep.equal([58 expect(properties).to.be.deep.equal([
59 {key: `0x${Buffer.from('electron').toString('hex')}`, value: `0x${Buffer.from('come bond').toString('hex')}`},59 {key: 'electron', value: 'come bond'},
60 {key: `0x${Buffer.from('black hole').toString('hex')}`, value: `0x${Buffer.from('').toString('hex')}`},60 {key: 'black_hole', value: ''},
61 ]);61 ]);
62 });62 });
63 });63 });
69 await expect(executeTransaction(69 await expect(executeTransaction(
70 api, 70 api,
71 alice, 71 alice,
72 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black hole'}]), 72 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]),
73 )).to.not.be.rejected;73 )).to.not.be.rejected;
7474
75 // Mutate the properties75 // Mutate the properties
76 await expect(executeTransaction(76 await expect(executeTransaction(
77 api, 77 api,
78 alice, 78 alice,
79 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black hole', value: 'LIGO'}]), 79 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]),
80 )).to.not.be.rejected;80 )).to.not.be.rejected;
8181
82 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();82 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
83 expect(properties).to.be.deep.equal([83 expect(properties).to.be.deep.equal([
84 {key: `0x${Buffer.from('electron').toString('hex')}`, value: `0x${Buffer.from('bonded').toString('hex')}`},84 {key: 'electron', value: 'bonded'},
85 {key: `0x${Buffer.from('black hole').toString('hex')}`, value: `0x${Buffer.from('LIGO').toString('hex')}`},85 {key: 'black_hole', value: 'LIGO'},
86 ]);86 ]);
87 });87 });
88 });88 });
94 await expect(executeTransaction(94 await expect(executeTransaction(
95 api, 95 api,
96 alice, 96 alice,
97 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black hole', value: 'LIGO'}]), 97 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]),
98 )).to.not.be.rejected;98 )).to.not.be.rejected;
9999
100 await expect(executeTransaction(100 await expect(executeTransaction(
103 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 103 api.tx.unique.deleteCollectionProperties(collection, ['electron']),
104 )).to.not.be.rejected;104 )).to.not.be.rejected;
105105
106 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();106 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
107 expect(properties).to.be.deep.equal([107 expect(properties).to.be.deep.equal([
108 {key: `0x${Buffer.from('black hole').toString('hex')}`, value: `0x${Buffer.from('LIGO').toString('hex')}`},108 {key: 'black_hole', value: 'LIGO'},
109 ]);109 ]);
110 });110 });
111 });111 });
126 await expect(executeTransaction(126 await expect(executeTransaction(
127 api, 127 api,
128 bob, 128 bob,
129 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black hole', value: 'LIGO'}]), 129 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]),
130 )).to.be.rejectedWith(/common\.NoPermission/);130 )).to.be.rejectedWith(/common\.NoPermission/);
131 131
132 const properties = (await api.query.common.collectionProperties(collection)).toJSON();132 const properties = (await api.query.common.collectionProperties(collection)).toJSON();
158 alice, 158 alice,
159 api.tx.unique.setCollectionProperties(collection, [159 api.tx.unique.setCollectionProperties(collection, [
160 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 160 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))},
161 {key: 'black hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 161 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))},
162 ]), 162 ]),
163 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);163 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);
164164
174 const propertiesToBeSet = [];174 const propertiesToBeSet = [];
175 for (let i = 0; i < 65; i++) {175 for (let i = 0; i < 65; i++) {
176 propertiesToBeSet.push({176 propertiesToBeSet.push({
177 key: 'electron ' + i,177 key: 'electron_' + i,
178 value: Math.random() > 0.5 ? 'high' : 'low',178 value: Math.random() > 0.5 ? 'high' : 'low',
179 });179 });
180 }180 }
191 });191 });
192 });192 });
193
194 it('Fails to set properties with invalid names', async () => {
195 await usingApi(async api => {
196 const collection = await createCollectionExpectSuccess();
197
198 const invalidProperties = [
199 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],
200 [{key: 'Mr.Sandman', value: 'Bring me a gene'}],
201 [{key: 'déjà vu', value: 'hmm...'}],
202 ];
203
204 for (let i = 0; i < invalidProperties.length; i++) {
205 await expect(executeTransaction(
206 api,
207 alice,
208 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]),
209 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
210 }
211
212 await expect(executeTransaction(
213 api,
214 alice,
215 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]),
216 ), `on rejecting an unnamed property`).to.be.rejectedWith(/common\.EmptyPropertyKey/);
217
218 await expect(executeTransaction(
219 api,
220 alice,
221 api.tx.unique.setCollectionProperties(collection, [
222 {key: 'CRISPR-Cas9', value: 'rewriting nature!'}
223 ]),
224 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
225
226 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');
227
228 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();
229 expect(properties).to.be.deep.equal([
230 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},
231 ]);
232
233 for (let i = 0; i < invalidProperties.length; i++) {
234 await expect(executeTransaction(
235 api,
236 alice,
237 api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)),
238 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
239 }
240 });
241 });
193});242});
194243
195// ---------- ACCESS RIGHTS244// ---------- ACCESS RIGHTS
228 api.tx.unique.setPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 277 api.tx.unique.setPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]),
229 )).to.not.be.rejected;278 )).to.not.be.rejected;
230279
231 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toJSON();280 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();
232 expect(propertyRights).to.be.deep.equal([281 expect(propertyRights).to.be.deep.equal([
233 {key: `0x${Buffer.from('skullduggery').toString('hex')}`, permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},282 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},
234 {key: `0x${Buffer.from('mindgame').toString('hex')}`, permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},283 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},
235 ]);284 ]);
236 });285 });
237 });286 });
252 api.tx.unique.setPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 301 api.tx.unique.setPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]),
253 )).to.not.be.rejected;302 )).to.not.be.rejected;
254303
255 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();304 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
256 expect(propertyRights).to.be.deep.equal([305 expect(propertyRights).to.be.deep.equal([
257 {key: `0x${Buffer.from('skullduggery').toString('hex')}`, permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},306 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
258 ]);307 ]);
259 });308 });
260 });309 });
290 const constitution = [];339 const constitution = [];
291 for (let i = 0; i < 65; i++) {340 for (let i = 0; i < 65; i++) {
292 constitution.push({341 constitution.push({
293 key: 'property ' + i,342 key: 'property_' + i,
294 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},343 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},
295 });344 });
296 }345 }
322 api.tx.unique.setPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 371 api.tx.unique.setPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]),
323 )).to.be.rejectedWith(/common\.NoPermission/);372 )).to.be.rejectedWith(/common\.NoPermission/);
324373
325 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();374 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
326 expect(propertyRights).to.deep.equal([375 expect(propertyRights).to.deep.equal([
327 {key: `0x${Buffer.from('skullduggery').toString('hex')}`, permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},376 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
328 ]);377 ]);
329 });378 });
330 });379 });
380
381 it('Prevents adding properties with invalid names', async () => {
382 await usingApi(async api => {
383 const collection = await createCollectionExpectSuccess();
384
385 const invalidProperties = [
386 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],
387 [{key: 'G#4', permission: {tokenOwner: true}}],
388 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],
389 ];
390
391 for (let i = 0; i < invalidProperties.length; i++) {
392 await expect(executeTransaction(
393 api,
394 alice,
395 api.tx.unique.setPropertyPermissions(collection, invalidProperties[i]),
396 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
397 }
398
399 await expect(executeTransaction(
400 api,
401 alice,
402 api.tx.unique.setPropertyPermissions(collection, [{key: '', permission: {}}]),
403 ), `on rejecting an unnamed property`).to.be.rejectedWith(/common\.EmptyPropertyKey/);
404
405 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string
406 await expect(executeTransaction(
407 api,
408 alice,
409 api.tx.unique.setPropertyPermissions(collection, [
410 {key: correctKey, permission: {collectionAdmin: true}},
411 ]),
412 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
413
414 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');
415
416 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();
417 expect(propertyRights).to.be.deep.equal([
418 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},
419 ]);
420 });
421 });
331});422});
332423
333// ---------- TOKEN PROPERTIES424// ---------- TOKEN PROPERTIES
361 await transferExpectSuccess(collection, token, alice, charlie);452 await transferExpectSuccess(collection, token, alice, charlie);
362 });453 });
363 454
364 it('Reads properties of a token', async () => {455 it('Reads yet empty properties of a token', async () => {
365 await usingApi(async api => {456 await usingApi(async api => {
366 const collection = await createCollectionExpectSuccess();457 const collection = await createCollectionExpectSuccess();
367 const token = await createItemExpectSuccess(alice, collection, 'NFT');458 const token = await createItemExpectSuccess(alice, collection, 'NFT');
381 let i = 0;472 let i = 0;
382 for (const permission of permissions) {473 for (const permission of permissions) {
383 for (const signer of permission.signers) {474 for (const signer of permission.signers) {
384 const key = i + ' ' + signer.address;475 const key = i + '_' + signer.address;
385 propertyKeys.push(key);476 propertyKeys.push(key);
386477
387 await expect(executeTransaction(478 await expect(executeTransaction(
400 i++;491 i++;
401 }492 }
402493
403 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];494 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];
404 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];495 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];
405 for (let i = 0; i < properties.length; i++) {496 for (let i = 0; i < properties.length; i++) {
406 expect(properties[i].value).to.be.equal(`0x${Buffer.from('Serotonin increase').toString('hex')}`);497 expect(properties[i].value).to.be.equal('Serotonin increase');
407 expect(tokensData[i].value).to.be.equal(`0x${Buffer.from('Serotonin increase').toString('hex')}`);498 expect(tokensData[i].value).to.be.equal('Serotonin increase');
408 }499 }
409 });500 });
410 });501 });
417 if (!permission.permission.mutable) continue;508 if (!permission.permission.mutable) continue;
418 509
419 for (const signer of permission.signers) {510 for (const signer of permission.signers) {
420 const key = i + ' ' + signer.address;511 const key = i + '_' + signer.address;
421 propertyKeys.push(key);512 propertyKeys.push(key);
422513
423 await expect(executeTransaction(514 await expect(executeTransaction(
442 i++;533 i++;
443 }534 }
444535
445 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];536 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];
446 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];537 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];
447 for (let i = 0; i < properties.length; i++) {538 for (let i = 0; i < properties.length; i++) {
448 expect(properties[i].value).to.be.equal(`0x${Buffer.from('Serotonin stable').toString('hex')}`);539 expect(properties[i].value).to.be.equal('Serotonin stable');
449 expect(tokensData[i].value).to.be.equal(`0x${Buffer.from('Serotonin stable').toString('hex')}`);540 expect(tokensData[i].value).to.be.equal('Serotonin stable');
450 }541 }
451 });542 });
452 });543 });
460 if (!permission.permission.mutable) continue;551 if (!permission.permission.mutable) continue;
461 552
462 for (const signer of permission.signers) {553 for (const signer of permission.signers) {
463 const key = i + ' ' + signer.address;554 const key = i + '_' + signer.address;
464 propertyKeys.push(key);555 propertyKeys.push(key);
465556
466 await expect(executeTransaction(557 await expect(executeTransaction(
532 await expect(executeTransaction(623 await expect(executeTransaction(
533 api, 624 api,
534 alice, 625 alice,
535 api.tx.unique.setPropertyPermissions(collection, [{key: i, permission: passage.permission}]), 626 api.tx.unique.setPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]),
536 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;627 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
537628
538 await expect(executeTransaction(629 await expect(executeTransaction(
539 api, 630 api,
540 signer, 631 signer,
541 api.tx.unique.setTokenProperties(collection, token, [{key: i, value: 'Serotonin increase'}]), 632 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]),
542 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;633 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
543634
544 i++;635 i++;
558 await expect(executeTransaction(649 await expect(executeTransaction(
559 api, 650 api,
560 forbiddance.sinner, 651 forbiddance.sinner,
561 api.tx.unique.setTokenProperties(collection, token, [{key: i, value: 'Serotonin down'}]), 652 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]),
562 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);653 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
563654
564 await expect(executeTransaction(655 await expect(executeTransaction(
565 api, 656 api,
566 forbiddance.sinner, 657 forbiddance.sinner,
567 api.tx.unique.deleteTokenProperties(collection, token, [forbiddance.permission]), 658 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]),
568 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);659 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
569 }660 }
570661
571 // todo RPC?
572 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();662 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
573 expect(properties.consumedSpace).to.be.equal(originalSpace);663 expect(properties.consumedSpace).to.be.equal(originalSpace);
574 });664 });
584 await expect(executeTransaction(674 await expect(executeTransaction(
585 api, 675 api,
586 permission.signers[0], 676 permission.signers[0],
587 api.tx.unique.setTokenProperties(collection, token, [{key: i, value: 'Serotonin down'}]), 677 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]),
588 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);678 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
589679
590 await expect(executeTransaction(680 await expect(executeTransaction(
594 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);684 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
595 }685 }
596686
597 // todo RPC?
598 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();687 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
599 expect(properties.consumedSpace).to.be.equal(originalSpace);688 expect(properties.consumedSpace).to.be.equal(originalSpace);
600 });689 });
632 api, 721 api,
633 alice, 722 alice,
634 api.tx.unique.setPropertyPermissions(collection, [723 api.tx.unique.setPropertyPermissions(collection, [
635 {key: 'a holy book', permission: {collectionAdmin: true, tokenOwner: true}}, 724 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}},
636 {key: 'young years', permission: {collectionAdmin: true, tokenOwner: true}},725 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},
637 ]), 726 ]),
638 ), 'on setting a new non-permitted property').to.not.be.rejected;727 ), 'on setting a new non-permitted property').to.not.be.rejected;
639728
643 await expect(executeTransaction(732 await expect(executeTransaction(
644 api, 733 api,
645 alice, 734 alice,
646 api.tx.unique.setCollectionProperties(collection, [{key: 'a holy book', value: 'word '.repeat(6554)}]), 735 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]),
647 )).to.be.rejected;736 )).to.be.rejected;
648 }737 }
649738
650 await expect(executeTransaction(739 await expect(executeTransaction(
651 api, 740 api,
652 alice, 741 alice,
653 api.tx.unique.setTokenProperties(collection, token, [742 api.tx.unique.setTokenProperties(collection, token, [
654 {key: 'a holy book', value: 'word '.repeat(3277)}, 743 {key: 'a_holy_book', value: 'word '.repeat(3277)},
655 {key: 'young years', value: 'neverending'.repeat(1490)},744 {key: 'young_years', value: 'neverending'.repeat(1490)},
656 ]), 745 ]),
657 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);746 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);
658747
659 expect((await api.rpc.unique.tokenProperties(collection, token, ['a holy book', 'young years'])).toJSON()).to.be.empty;748 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young years'])).toJSON()).to.be.empty;
660 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();749 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
661 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);750 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);
662 });751 });
modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
6 createCollectionExpectSuccess,6 createCollectionExpectSuccess,
7 createItemExpectFailure,7 createItemExpectFailure,
8 createItemExpectSuccess,8 createItemExpectSuccess,
9 getBalance,
9 getTokenOwner,10 getTokenOwner,
10 getTopmostTokenOwner,11 getTopmostTokenOwner,
11 normalizeAccountId,12 normalizeAccountId,
12 setCollectionLimitsExpectSuccess,13 setCollectionLimitsExpectSuccess,
13 transferExpectSuccess,14 transferExpectSuccess,
15 transferFromExpectSuccess,
14} from '../util/helpers';16} from '../util/helpers';
15import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
1618
25 });27 });
26 });28 });
2729
28 it('Allows the owner to successfully unnest a token', async () => {30 it('NFT: allows the owner to successfully unnest a token', async () => {
29 await usingApi(async api => {31 await usingApi(async api => {
30 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});32 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
31 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});33 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
40 api,42 api,
41 alice,43 alice,
42 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),44 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1),
43 )).to.not.be.rejected;45 ), 'while unnesting').to.not.be.rejected;
44 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});46 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});
4547
46 // Nest and burn48 // Nest and burn
47 await transferExpectSuccess(collection, nestedToken, alice, targetAddress);49 await transferExpectSuccess(collection, nestedToken, alice, targetAddress);
48 await expect(executeTransaction(50 await expect(executeTransaction(
49 api,51 api,
50 alice,52 alice,
51 api.tx.unique.burnFrom(collection, normalizeAccountId(alice.address), nestedToken, 1),53 api.tx.unique.burnFrom(collection, normalizeAccountId(targetAddress), nestedToken, 1),
52 )).to.not.be.rejected;54 ), 'while burning').to.not.be.rejected;
53 await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected; // 'owner == null'55 await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected;
54 });56 });
55 });57 });
5658
57 // todo refungible-fungible test just in case59 it('Fungible: allows the owner to successfully unnest a token', async () => {
60 await usingApi(async api => {
61 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
62 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
63 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
64 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};
65
66 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
67 const nestedToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');
68
69 // Nest and unnest
70 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');
71 await transferFromExpectSuccess(collectionFT, nestedToken, alice, targetAddress, alice, 1, 'Fungible');
72
73 // Nest and burn
74 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');
75 const balanceBefore = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);
76 await expect(executeTransaction(
77 api,
78 alice,
79 api.tx.unique.burnFrom(collectionFT, normalizeAccountId(targetAddress), nestedToken, 1),
80 ), 'while burning').to.not.be.rejected;
81 const balanceAfter = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken);
82 expect(balanceAfter + BigInt(1)).to.be.equal(balanceBefore);
83 });
84 });
85
86 it('ReFungible: allows the owner to successfully unnest a token', async () => {
87 await usingApi(async api => {
88 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
89 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
90 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
91 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)};
92
93 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
94 const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');
95
96 // Nest and unnest
97 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');
98 await transferFromExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, alice, 1, 'ReFungible');
99
100 // Nest and burn
101 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');
102 await expect(executeTransaction(
103 api,
104 alice,
105 api.tx.unique.burnFrom(collectionRFT, normalizeAccountId(targetAddress), nestedToken, 1),
106 ), 'while burning').to.not.be.rejected;
107 const balance = await getBalance(api, collectionRFT, normalizeAccountId(targetAddress), nestedToken);
108 expect(balance).to.be.equal(0n);
109 });
110 });
58});111});
59112
60describe('Negative Test: Unnesting', () => {113describe('Negative Test: Unnesting', () => {
80 api,133 api,
81 bob,134 bob,
82 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1),135 api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1),
83 )).to.be.rejectedWith(/^common\.ApprovedValueTooLow$/);136 ), 'while unnesting').to.be.rejectedWith(/^structure\.DepthLimit$/); // todo ApprovedValueTooLow?
84 //await transferFromExpectSuccess(collection, nestedToken, bob, targetAddress, {Substrate: bob.address});
85 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});137 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
86138
87 // Try to burn139 // Try to burn
88 await expect(executeTransaction(140 await expect(executeTransaction(
89 api,141 api,
90 bob,142 bob,
91 api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),143 api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1),
92 )).to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);144 ), 'while burning').to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/);
93 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});145 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});
94 });146 });
95 });147 });
96
97 it('Disallows excessive token nesting', async () => {
98 await usingApi(async api => {
99 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
100 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
101 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
102
103 // Create a nested token matryoshka
104 const nestedToken1 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});
105 const nestedToken2 = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken1)});
106 // The nesting depth is limited by 2
107 await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken2)});
108
109 expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});
110 });
111 });
112148
113 // todo another test for creating excessive depth matryoshka with Ethereum, move this one to nest ^149 // todo another test for creating excessive depth matryoshka with Ethereum?
114150
115 // Recursive nesting151 // Recursive nesting
116 it('Prevents ouroboros creation', async () => {152 it('Prevents Ouroboros creation', async () => {
117 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});153 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
118 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});154 await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'});
119 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');155 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
903 const from = normalizeAccountId(accountFrom);903 const from = normalizeAccountId(accountFrom);
904 const to = normalizeAccountId(accountTo);904 const to = normalizeAccountId(accountTo);
905 let balanceBefore = 0n;905 let balanceBefore = 0n;
906 if (type === 'Fungible') {906 if (type === 'Fungible' || type === 'ReFungible') {
907 balanceBefore = await getBalance(api, collectionId, to, tokenId);907 balanceBefore = await getBalance(api, collectionId, to, tokenId);
908 }908 }
909 const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom), to, collectionId, tokenId, value);909 const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom), to, collectionId, tokenId, value);
923 }923 }
924 }924 }
925 if (type === 'ReFungible') {925 if (type === 'ReFungible') {
926 expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(BigInt(value));926 expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(balanceBefore + BigInt(value));
927 }927 }
928 });928 });
929}929}