difftreelog
test(structure) refactor properties and nesting
in: master
7 files changed
tests/package.jsondiffbeforeafterboth33 "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",tests/src/nesting/graphs.test.tsdiffbeforeafterboth14 * ```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);191920 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}343435describe('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);404041 // to self41 // to self42 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 graph45 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 });tests/src/nesting/migration-check.test.tsdiffbeforeafterboth18 });18 });19 });19 });202021 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 }tests/src/nesting/nest.test.tsdiffbeforeafterboth5import {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 });313132 // ---------- Non-Fungible ----------3334 // todo refactor names35 // todo remove excessive bundling, leave it for a single test36 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 // Nest51 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()});545055 // Move bundle to different user51 // Move bundle to different user56 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});52 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});63 });59 });64 });60 });6162 // ---------- Non-Fungible ----------6364 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');6970 // Create a nested token71 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()});7475 // Create a token to be nested and nest76 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 });658266 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()});769377 // Create a token to be nested94 // Create a token to be nested and nest78 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');95 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');79 80 // Nest81 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()});8485 // Move bundle to different user86 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 // Unnest91 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 });9510196 // ---------- Fungible ----------102 // ---------- Fungible ----------9710398 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;113119114 // Create a token to be nested120 // Nest a new token115 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');121 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');116 // Nest117 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');122 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');118 // Move bundle to different user119 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});120 // Unnest121 await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');122 });123 });123 });124 });124125125 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;141142142 // Create a token to be nested143 // Nest a new token143 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');144 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');144 // Nest145 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');145 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');146 // Move bundle to different user147 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});148 // Unnest149 await transferFromExpectSuccess(collectionFT, newToken, bob, targetAddress, {Substrate: bob.address}, 1, 'Fungible');150 });146 });151 });147 });152148153 // ---------- Re-Fungible ----------149 // ---------- Re-Fungible ----------154150155 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;170166171 // Create a token to be nested167 // Nest a new token172 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');168 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');173 // Nest174 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');169 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');175 // Move bundle to different user176 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});177 // Unnest178 await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');179 });170 });180 });171 });181172182 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;198189199 // Create a token to be nested190 // Nest a new token200 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');191 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');201 // Nest202 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');192 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');203 // Move bundle to different user204 await transferExpectSuccess(collectionNFT, targetToken, alice, {Substrate: bob.address});205 // Unnest206 await transferFromExpectSuccess(collectionRFT, newToken, bob, targetAddress, {Substrate: bob.address}, 100, 'ReFungible');207 });193 });208 });194 });209});195});216 });202 });217 });203 });204205 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');210211 // Create a nested-token matryoshka212 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 2215 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?223224 expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});225 });226 });218227219 // ---------- Non-Fungible ----------228 // ---------- Non-Fungible ----------220229230 {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$/);233245234 // Create a token to be nested246 // Create a token to be nested235 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');247 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');236 // Try to nest248 // Try to nest237 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 all238 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 });242259243 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');254271255 // Try to create a nested token in the wrong collection272 // Try to create a nested token in the wrong collection256 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?257281258 // Try to create and nest a token in the wrong collection282 // Try to create and nest a token in the wrong collection259 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 });264293265 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');276305277 // Try to create a nested token in the wrong collection306 // Try to create a nested token in the wrong collection278 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/);279315280 // Try to create and nest a token in the wrong collection316 // Try to create and nest a token in the wrong collection281 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 });286327287 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');294335295 // Try to create a nested token in the wrong collection336 // Try to create a nested token in the wrong collection296 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/);297345298 // Try to create and nest a token in the wrong collection346 // Try to create and nest a token in the wrong collection299 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$/);322378323 // Create a token to be nested379 // Create a token to be nested324 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');380 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');325 // Try to nest381 // Try to nest326 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 });329390330 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/);351415352 // Try to create and nest a token in the wrong collection416 // Try to create and nest a token in the wrong collection353 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 });357426358 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?362431363 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 });385454386 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$/);427496428 // Create a token to be nested497 // Create a token to be nested432 });501 });433 });502 });434503435 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;456525457 // Try to create and nest a token in the wrong collection526 // Try to create and nest a token in the wrong collection460 });529 });461 });530 });462531463 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;484553485 // Try to create and nest a token in the wrong collection554 // Try to create and nest a token in the wrong collection503 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;508577509 // Try to create and nest a token in the wrong collection578 // Try to create and nest a token in the wrong collectiontests/src/nesting/properties.test.tsdiffbeforeafterboth51 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;565657 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;747475 // Mutate the properties75 // Mutate the properties76 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;818182 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;9999100 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;105105106 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/);164164174 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 });193194 it('Fails to set properties with invalid names', async () => {195 await usingApi(async api => {196 const collection = await createCollectionExpectSuccess();197198 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 ];203204 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 }211212 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/);217218 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;225226 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');227228 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 ]);232233 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});194243195// ---------- ACCESS RIGHTS244// ---------- ACCESS RIGHTS228 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;230279231 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;254303255 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/);324373325 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 });380381 it('Prevents adding properties with invalid names', async () => {382 await usingApi(async api => {383 const collection = await createCollectionExpectSuccess();384385 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 ];390391 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 }398399 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/);404405 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string406 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;413414 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');415416 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});332423333// ---------- TOKEN PROPERTIES424// ---------- TOKEN PROPERTIES361 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);386477387 await expect(executeTransaction(478 await expect(executeTransaction(400 i++;491 i++;401 }492 }402493403 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);422513423 await expect(executeTransaction(514 await expect(executeTransaction(442 i++;533 i++;443 }534 }444535445 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);465556466 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;537628538 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;543634544 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/);563654564 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 }570661571 // 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/);589679590 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 }596686597 // 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;639728643 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 }649738650 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/);658747659 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 });tests/src/nesting/unnest.test.tsdiffbeforeafterboth6 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';161825 });27 });26 });28 });272928 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});454746 // Nest and burn48 // Nest and burn47 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 });565857 // 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)};6566 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});67 const nestedToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');6869 // Nest and unnest70 await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible');71 await transferFromExpectSuccess(collectionFT, nestedToken, alice, targetAddress, alice, 1, 'Fungible');7273 // Nest and burn74 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 });8586 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)};9293 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});94 const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');9596 // Nest and unnest97 await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible');98 await transferFromExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, alice, 1, 'ReFungible');99100 // Nest and burn101 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});5911260describe('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()});8613887 // Try to burn139 // Try to burn88 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 });9697 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');102103 // Create a nested token matryoshka104 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 2107 await createItemExpectFailure(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, nestedToken2)});108109 expect(await getTopmostTokenOwner(api, collection, nestedToken2)).to.be.deep.equal({Substrate: alice.address});110 });111 });112148113 // 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?114150115 // Recursive nesting151 // Recursive nesting116 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');tests/src/util/helpers.tsdiffbeforeafterboth903 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}