difftreelog
test manage properties of a nested token
in: master
1 file changed
tests/src/nesting/properties.test.tsdiffbeforeafterboth1import {expect} from 'chai';2import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {4 addCollectionAdminExpectSuccess,5 createCollectionExpectSuccess,6 createItemExpectSuccess,7 getCreateCollectionResult,8 transferExpectSuccess,9} from '../util/helpers';10import {IKeyringPair} from '@polkadot/types/types';1112let alice: IKeyringPair;13let bob: IKeyringPair;14let charlie: IKeyringPair;1516describe('Composite Properties Test', () => {17 before(async () => {18 await usingApi(async (api, privateKeyWrapper) => {19 alice = privateKeyWrapper('//Alice');20 bob = privateKeyWrapper('//Bob');21 });22 });2324 it('Makes sure collectionById supplies required fields', async () => {25 await usingApi(async api => {26 const collectionId = await createCollectionExpectSuccess();2728 const collectionOption = await api.rpc.unique.collectionById(collectionId);29 expect(collectionOption.isSome).to.be.true;30 let collection = collectionOption.unwrap();31 expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;32 expect(collection.properties.toHuman()).to.be.empty;3334 const propertyPermissions = [35 {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},36 {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},37 ];38 await expect(executeTransaction(39 api, 40 alice, 41 api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), 42 )).to.not.be.rejected;4344 const collectionProperties = [45 {key: 'black_hole', value: 'LIGO'},46 {key: 'electron', value: 'come bond'}, 47 ];48 await expect(executeTransaction(49 api, 50 alice, 51 api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 52 )).to.not.be.rejected;5354 collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();55 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);56 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);57 });58 });59});6061// ---------- COLLECTION PROPERTIES6263describe('Integration Test: Collection Properties', () => {64 before(async () => {65 await usingApi(async (api, privateKeyWrapper) => {66 alice = privateKeyWrapper('//Alice');67 bob = privateKeyWrapper('//Bob');68 });69 });7071 it('Reads properties from a collection', async () => {72 await usingApi(async api => {73 const collection = await createCollectionExpectSuccess();74 const properties = (await api.query.common.collectionProperties(collection)).toJSON();75 expect(properties.map).to.be.empty;76 expect(properties.consumedSpace).to.equal(0);77 });78 });7980 it('Sets properties for a collection', async () => {81 await usingApi(async api => {82 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));83 const {collectionId} = getCreateCollectionResult(events);8485 // As owner86 await expect(executeTransaction(87 api, 88 bob, 89 api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 90 )).to.not.be.rejected;9192 await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);9394 // As administrator95 await expect(executeTransaction(96 api, 97 alice, 98 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 99 )).to.not.be.rejected;100101 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();102 expect(properties).to.be.deep.equal([103 {key: 'electron', value: 'come bond'},104 {key: 'black_hole', value: ''},105 ]);106 });107 });108109 it('Check valid names for collection properties keys', async () => {110 await usingApi(async api => {111 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));112 const {collectionId} = getCreateCollectionResult(events);113114 // alpha symbols115 await expect(executeTransaction(116 api, 117 bob, 118 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 119 )).to.not.be.rejected;120121 // numeric symbols122 await expect(executeTransaction(123 api, 124 bob, 125 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 126 )).to.not.be.rejected;127128 // underscore symbol129 await expect(executeTransaction(130 api, 131 bob, 132 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 133 )).to.not.be.rejected;134135 // dash symbol136 await expect(executeTransaction(137 api, 138 bob, 139 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 140 )).to.not.be.rejected;141142 // underscore symbol143 await expect(executeTransaction(144 api, 145 bob, 146 api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 147 )).to.not.be.rejected;148149 const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];150 const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();151 expect(properties).to.be.deep.equal([152 {key: 'alpha', value: ''},153 {key: '123', value: ''},154 {key: 'black_hole', value: ''},155 {key: 'semi-automatic', value: ''},156 {key: 'build.rs', value: ''},157 ]);158 });159 });160161 it('Changes properties of a collection', async () => {162 await usingApi(async api => {163 const collection = await createCollectionExpectSuccess();164165 await expect(executeTransaction(166 api, 167 alice, 168 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 169 )).to.not.be.rejected;170171 // Mutate the properties172 await expect(executeTransaction(173 api, 174 alice, 175 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 176 )).to.not.be.rejected;177178 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();179 expect(properties).to.be.deep.equal([180 {key: 'electron', value: 'bonded'},181 {key: 'black_hole', value: 'LIGO'},182 ]);183 });184 });185186 it('Deletes properties of a collection', async () => {187 await usingApi(async api => {188 const collection = await createCollectionExpectSuccess();189190 await expect(executeTransaction(191 api, 192 alice, 193 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 194 )).to.not.be.rejected;195196 await expect(executeTransaction(197 api, 198 alice, 199 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 200 )).to.not.be.rejected;201202 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();203 expect(properties).to.be.deep.equal([204 {key: 'black_hole', value: 'LIGO'},205 ]);206 });207 });208});209210describe('Negative Integration Test: Collection Properties', () => {211 before(async () => {212 await usingApi(async (api, privateKeyWrapper) => {213 alice = privateKeyWrapper('//Alice');214 bob = privateKeyWrapper('//Bob');215 });216 });217 218 it('Fails to set properties in a collection if not its onwer/administrator', async () => {219 await usingApi(async api => {220 const collection = await createCollectionExpectSuccess();221222 await expect(executeTransaction(223 api, 224 bob, 225 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 226 )).to.be.rejectedWith(/common\.NoPermission/);227 228 const properties = (await api.query.common.collectionProperties(collection)).toJSON();229 expect(properties.map).to.be.empty;230 expect(properties.consumedSpace).to.equal(0);231 });232 });233 234 it('Fails to set properties that exceed the limits', async () => {235 await usingApi(async api => {236 const collection = await createCollectionExpectSuccess();237 const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 238239 // Mute the general tx parsing error, too many bytes to process240 {241 console.error = () => {};242 await expect(executeTransaction(243 api, 244 alice, 245 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 246 )).to.be.rejected;247 }248249 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();250 expect(properties).to.be.empty;251252 await expect(executeTransaction(253 api, 254 alice, 255 api.tx.unique.setCollectionProperties(collection, [256 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 257 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 258 ]), 259 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);260261 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();262 expect(properties).to.be.empty;263 });264 });265 266 it('Fails to set more properties than it is allowed', async () => {267 await usingApi(async api => {268 const collection = await createCollectionExpectSuccess();269270 const propertiesToBeSet = [];271 for (let i = 0; i < 65; i++) {272 propertiesToBeSet.push({273 key: 'electron_' + i,274 value: Math.random() > 0.5 ? 'high' : 'low',275 });276 }277278 await expect(executeTransaction(279 api, 280 alice, 281 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 282 )).to.be.rejectedWith(/common\.PropertyLimitReached/);283284 const properties = (await api.query.common.collectionProperties(collection)).toJSON();285 expect(properties.map).to.be.empty;286 expect(properties.consumedSpace).to.equal(0);287 });288 });289290 it('Fails to set properties with invalid names', async () => {291 await usingApi(async api => {292 const collection = await createCollectionExpectSuccess();293294 const invalidProperties = [295 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],296 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],297 [{key: 'déjà vu', value: 'hmm...'}],298 ];299300 for (let i = 0; i < invalidProperties.length; i++) {301 await expect(executeTransaction(302 api, 303 alice, 304 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 305 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);306 }307308 await expect(executeTransaction(309 api, 310 alice, 311 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 312 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);313314 await expect(executeTransaction(315 api, 316 alice, 317 api.tx.unique.setCollectionProperties(collection, [318 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},319 ]), 320 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;321322 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');323324 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();325 expect(properties).to.be.deep.equal([326 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},327 ]);328329 for (let i = 0; i < invalidProperties.length; i++) {330 await expect(executeTransaction(331 api, 332 alice, 333 api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 334 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);335 }336 });337 });338});339340// ---------- ACCESS RIGHTS341342describe('Integration Test: Access Rights to Token Properties', () => {343 before(async () => {344 await usingApi(async (api, privateKeyWrapper) => {345 alice = privateKeyWrapper('//Alice');346 bob = privateKeyWrapper('//Bob');347 });348 });349 350 it('Reads access rights to properties of a collection', async () => {351 await usingApi(async api => {352 const collection = await createCollectionExpectSuccess();353 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();354 expect(propertyRights).to.be.empty;355 });356 });357 358 it('Sets access rights to properties of a collection', async () => {359 await usingApi(async api => {360 const collection = await createCollectionExpectSuccess();361362 await expect(executeTransaction(363 api, 364 alice, 365 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 366 )).to.not.be.rejected;367368 await addCollectionAdminExpectSuccess(alice, collection, bob.address);369370 await expect(executeTransaction(371 api, 372 alice, 373 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 374 )).to.not.be.rejected;375376 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();377 expect(propertyRights).to.be.deep.equal([378 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},379 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},380 ]);381 });382 });383 384 it('Changes access rights to properties of a collection', async () => {385 await usingApi(async api => {386 const collection = await createCollectionExpectSuccess();387388 await expect(executeTransaction(389 api, 390 alice, 391 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 392 )).to.not.be.rejected;393394 await expect(executeTransaction(395 api, 396 alice, 397 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 398 )).to.not.be.rejected;399400 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();401 expect(propertyRights).to.be.deep.equal([402 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},403 ]);404 });405 });406});407408describe('Negative Integration Test: Access Rights to Token Properties', () => {409 before(async () => {410 await usingApi(async (api, privateKeyWrapper) => {411 alice = privateKeyWrapper('//Alice');412 bob = privateKeyWrapper('//Bob');413 });414 });415416 it('Prevents from setting access rights to properties of a collection if not an onwer/admin', async () => {417 await usingApi(async api => {418 const collection = await createCollectionExpectSuccess();419420 await expect(executeTransaction(421 api, 422 bob, 423 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 424 )).to.be.rejectedWith(/common\.NoPermission/);425426 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();427 expect(propertyRights).to.be.empty;428 });429 });430431 it('Prevents from adding too many possible properties', async () => {432 await usingApi(async api => {433 const collection = await createCollectionExpectSuccess();434435 const constitution = [];436 for (let i = 0; i < 65; i++) {437 constitution.push({438 key: 'property_' + i,439 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},440 });441 }442443 await expect(executeTransaction(444 api, 445 alice, 446 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 447 )).to.be.rejectedWith(/common\.PropertyLimitReached/);448449 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();450 expect(propertyRights).to.be.empty;451 });452 });453454 it('Prevents access rights to be modified if constant', async () => {455 await usingApi(async api => {456 const collection = await createCollectionExpectSuccess();457458 await expect(executeTransaction(459 api, 460 alice, 461 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 462 )).to.not.be.rejected;463464 await expect(executeTransaction(465 api, 466 alice, 467 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 468 )).to.be.rejectedWith(/common\.NoPermission/);469470 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();471 expect(propertyRights).to.deep.equal([472 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},473 ]);474 });475 });476477 it('Prevents adding properties with invalid names', async () => {478 await usingApi(async api => {479 const collection = await createCollectionExpectSuccess();480481 const invalidProperties = [482 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],483 [{key: 'G#4', permission: {tokenOwner: true}}],484 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],485 ];486487 for (let i = 0; i < invalidProperties.length; i++) {488 await expect(executeTransaction(489 api, 490 alice, 491 api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 492 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);493 }494495 await expect(executeTransaction(496 api, 497 alice, 498 api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 499 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);500501 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string502 await expect(executeTransaction(503 api, 504 alice, 505 api.tx.unique.setTokenPropertyPermissions(collection, [506 {key: correctKey, permission: {collectionAdmin: true}},507 ]), 508 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;509510 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');511512 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();513 expect(propertyRights).to.be.deep.equal([514 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},515 ]);516 });517 });518});519520// ---------- TOKEN PROPERTIES521522describe('Integration Test: Token Properties', () => {523 let collection: number;524 let token: number;525 let permissions: {permission: any, signers: IKeyringPair[]}[];526527 before(async () => {528 await usingApi(async (api, privateKeyWrapper) => {529 alice = privateKeyWrapper('//Alice');530 bob = privateKeyWrapper('//Bob');531 charlie = privateKeyWrapper('//Charlie');532533 permissions = [534 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},535 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},536 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},537 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},538 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},539 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},540 ];541 });542 });543544 beforeEach(async () => {545 await usingApi(async () => {546 collection = await createCollectionExpectSuccess();547 token = await createItemExpectSuccess(alice, collection, 'NFT');548 await addCollectionAdminExpectSuccess(alice, collection, bob.address);549 await transferExpectSuccess(collection, token, alice, charlie);550 });551 });552 553 it('Reads yet empty properties of a token', async () => {554 await usingApi(async api => {555 const collection = await createCollectionExpectSuccess();556 const token = await createItemExpectSuccess(alice, collection, 'NFT');557 558 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();559 expect(properties.map).to.be.empty;560 expect(properties.consumedSpace).to.be.equal(0);561562 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;563 expect(tokenData).to.be.empty;564 });565 });566567 it('Assigns properties to a token according to permissions', async () => {568 await usingApi(async api => {569 const propertyKeys: string[] = [];570 let i = 0;571 for (const permission of permissions) {572 for (const signer of permission.signers) {573 const key = i + '_' + signer.address;574 propertyKeys.push(key);575576 await expect(executeTransaction(577 api, 578 alice, 579 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 580 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;581582 await expect(executeTransaction(583 api, 584 signer, 585 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 586 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;587 }588589 i++;590 }591592 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];593 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];594 for (let i = 0; i < properties.length; i++) {595 expect(properties[i].value).to.be.equal('Serotonin increase');596 expect(tokensData[i].value).to.be.equal('Serotonin increase');597 }598 });599 });600601 it('Changes properties of a token according to permissions', async () => {602 await usingApi(async api => {603 const propertyKeys: string[] = [];604 let i = 0;605 for (const permission of permissions) {606 if (!permission.permission.mutable) continue;607 608 for (const signer of permission.signers) {609 const key = i + '_' + signer.address;610 propertyKeys.push(key);611612 await expect(executeTransaction(613 api, 614 alice, 615 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 616 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;617618 await expect(executeTransaction(619 api, 620 signer, 621 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 622 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;623624 await expect(executeTransaction(625 api, 626 signer, 627 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 628 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;629 }630631 i++;632 }633634 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];635 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];636 for (let i = 0; i < properties.length; i++) {637 expect(properties[i].value).to.be.equal('Serotonin stable');638 expect(tokensData[i].value).to.be.equal('Serotonin stable');639 }640 });641 });642643 it('Deletes properties of a token according to permissions', async () => {644 await usingApi(async api => {645 const propertyKeys: string[] = [];646 let i = 0;647648 for (const permission of permissions) {649 if (!permission.permission.mutable) continue;650 651 for (const signer of permission.signers) {652 const key = i + '_' + signer.address;653 propertyKeys.push(key);654655 await expect(executeTransaction(656 api, 657 alice, 658 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 659 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;660661 await expect(executeTransaction(662 api, 663 signer, 664 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 665 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;666667 await expect(executeTransaction(668 api, 669 signer, 670 api.tx.unique.deleteTokenProperties(collection, token, [key]), 671 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;672 }673 674 i++;675 }676677 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];678 expect(properties).to.be.empty;679 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];680 expect(tokensData).to.be.empty;681 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);682 });683 });684});685686describe('Negative Integration Test: Token Properties', () => {687 let collection: number;688 let token: number;689 let originalSpace: number;690 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];691692 before(async () => {693 await usingApi(async (api, privateKeyWrapper) => {694 alice = privateKeyWrapper('//Alice');695 bob = privateKeyWrapper('//Bob');696 charlie = privateKeyWrapper('//Charlie');697 const dave = privateKeyWrapper('//Dave');698699 constitution = [700 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},701 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},702 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},703 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},704 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},705 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},706 ];707 });708 });709710 beforeEach(async () => {711 collection = await createCollectionExpectSuccess();712 token = await createItemExpectSuccess(alice, collection, 'NFT');713 await addCollectionAdminExpectSuccess(alice, collection, bob.address);714 await transferExpectSuccess(collection, token, alice, charlie);715 716 await usingApi(async api => {717 let i = 0;718 for (const passage of constitution) {719 const signer = passage.signers[0];720 721 await expect(executeTransaction(722 api, 723 alice, 724 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 725 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;726727 await expect(executeTransaction(728 api, 729 signer, 730 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 731 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;732733 i++;734 }735736 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;737 });738 });739740 it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {741 await usingApi(async api => {742 let i = -1;743 for (const forbiddance of constitution) {744 i++;745 if (!forbiddance.permission.mutable) continue;746747 await expect(executeTransaction(748 api, 749 forbiddance.sinner, 750 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 751 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);752753 await expect(executeTransaction(754 api, 755 forbiddance.sinner, 756 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 757 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);758 }759760 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();761 expect(properties.consumedSpace).to.be.equal(originalSpace);762 });763 });764765 it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {766 await usingApi(async api => {767 let i = -1;768 for (const permission of constitution) {769 i++;770 if (permission.permission.mutable) continue;771772 await expect(executeTransaction(773 api, 774 permission.signers[0], 775 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 776 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);777778 await expect(executeTransaction(779 api, 780 permission.signers[0], 781 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 782 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);783 }784785 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();786 expect(properties.consumedSpace).to.be.equal(originalSpace);787 });788 });789790 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {791 await usingApi(async api => {792 await expect(executeTransaction(793 api, 794 alice, 795 api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 796 ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);797 798 await expect(executeTransaction(799 api, 800 alice, 801 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 802 ), 'on setting a new non-permitted property').to.not.be.rejected;803804 await expect(executeTransaction(805 api, 806 alice, 807 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 808 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);809810 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;811 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();812 expect(properties.consumedSpace).to.be.equal(originalSpace);813 });814 });815816 it('Forbids adding too many properties to a token', async () => {817 await usingApi(async api => {818 await expect(executeTransaction(819 api, 820 alice, 821 api.tx.unique.setTokenPropertyPermissions(collection, [822 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 823 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},824 ]), 825 ), 'on setting a new non-permitted property').to.not.be.rejected;826827 // Mute the general tx parsing error828 {829 console.error = () => {};830 await expect(executeTransaction(831 api, 832 alice, 833 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 834 )).to.be.rejected;835 }836837 await expect(executeTransaction(838 api, 839 alice, 840 api.tx.unique.setTokenProperties(collection, token, [841 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 842 {key: 'young_years', value: 'neverending'.repeat(1490)},843 ]), 844 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);845846 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;847 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();848 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);849 });850 });851});1import {expect} from 'chai';2import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {4 addCollectionAdminExpectSuccess,5 createCollectionExpectSuccess,6 setCollectionPermissionsExpectSuccess,7 createItemExpectSuccess,8 getCreateCollectionResult,9 transferExpectSuccess,10} from '../util/helpers';11import {IKeyringPair} from '@polkadot/types/types';12import { tokenIdToAddress } from '../eth/util/helpers';1314let alice: IKeyringPair;15let bob: IKeyringPair;16let charlie: IKeyringPair;1718describe('Composite Properties Test', () => {19 before(async () => {20 await usingApi(async (api, privateKeyWrapper) => {21 alice = privateKeyWrapper('//Alice');22 bob = privateKeyWrapper('//Bob');23 });24 });2526 it('Makes sure collectionById supplies required fields', async () => {27 await usingApi(async api => {28 const collectionId = await createCollectionExpectSuccess();2930 const collectionOption = await api.rpc.unique.collectionById(collectionId);31 expect(collectionOption.isSome).to.be.true;32 let collection = collectionOption.unwrap();33 expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;34 expect(collection.properties.toHuman()).to.be.empty;3536 const propertyPermissions = [37 {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},38 {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},39 ];40 await expect(executeTransaction(41 api, 42 alice, 43 api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), 44 )).to.not.be.rejected;4546 const collectionProperties = [47 {key: 'black_hole', value: 'LIGO'},48 {key: 'electron', value: 'come bond'}, 49 ];50 await expect(executeTransaction(51 api, 52 alice, 53 api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 54 )).to.not.be.rejected;5556 collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();57 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);58 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);59 });60 });61});6263// ---------- COLLECTION PROPERTIES6465describe('Integration Test: Collection Properties', () => {66 before(async () => {67 await usingApi(async (api, privateKeyWrapper) => {68 alice = privateKeyWrapper('//Alice');69 bob = privateKeyWrapper('//Bob');70 });71 });7273 it('Reads properties from a collection', async () => {74 await usingApi(async api => {75 const collection = await createCollectionExpectSuccess();76 const properties = (await api.query.common.collectionProperties(collection)).toJSON();77 expect(properties.map).to.be.empty;78 expect(properties.consumedSpace).to.equal(0);79 });80 });8182 it('Sets properties for a collection', async () => {83 await usingApi(async api => {84 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));85 const {collectionId} = getCreateCollectionResult(events);8687 // As owner88 await expect(executeTransaction(89 api, 90 bob, 91 api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 92 )).to.not.be.rejected;9394 await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);9596 // As administrator97 await expect(executeTransaction(98 api, 99 alice, 100 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 101 )).to.not.be.rejected;102103 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();104 expect(properties).to.be.deep.equal([105 {key: 'electron', value: 'come bond'},106 {key: 'black_hole', value: ''},107 ]);108 });109 });110111 it('Check valid names for collection properties keys', async () => {112 await usingApi(async api => {113 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));114 const {collectionId} = getCreateCollectionResult(events);115116 // alpha symbols117 await expect(executeTransaction(118 api, 119 bob, 120 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 121 )).to.not.be.rejected;122123 // numeric symbols124 await expect(executeTransaction(125 api, 126 bob, 127 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 128 )).to.not.be.rejected;129130 // underscore symbol131 await expect(executeTransaction(132 api, 133 bob, 134 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 135 )).to.not.be.rejected;136137 // dash symbol138 await expect(executeTransaction(139 api, 140 bob, 141 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 142 )).to.not.be.rejected;143144 // underscore symbol145 await expect(executeTransaction(146 api, 147 bob, 148 api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 149 )).to.not.be.rejected;150151 const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];152 const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();153 expect(properties).to.be.deep.equal([154 {key: 'alpha', value: ''},155 {key: '123', value: ''},156 {key: 'black_hole', value: ''},157 {key: 'semi-automatic', value: ''},158 {key: 'build.rs', value: ''},159 ]);160 });161 });162163 it('Changes properties of a collection', async () => {164 await usingApi(async api => {165 const collection = await createCollectionExpectSuccess();166167 await expect(executeTransaction(168 api, 169 alice, 170 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 171 )).to.not.be.rejected;172173 // Mutate the properties174 await expect(executeTransaction(175 api, 176 alice, 177 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 178 )).to.not.be.rejected;179180 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();181 expect(properties).to.be.deep.equal([182 {key: 'electron', value: 'bonded'},183 {key: 'black_hole', value: 'LIGO'},184 ]);185 });186 });187188 it('Deletes properties of a collection', async () => {189 await usingApi(async api => {190 const collection = await createCollectionExpectSuccess();191192 await expect(executeTransaction(193 api, 194 alice, 195 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 196 )).to.not.be.rejected;197198 await expect(executeTransaction(199 api, 200 alice, 201 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 202 )).to.not.be.rejected;203204 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();205 expect(properties).to.be.deep.equal([206 {key: 'black_hole', value: 'LIGO'},207 ]);208 });209 });210});211212describe('Negative Integration Test: Collection Properties', () => {213 before(async () => {214 await usingApi(async (api, privateKeyWrapper) => {215 alice = privateKeyWrapper('//Alice');216 bob = privateKeyWrapper('//Bob');217 });218 });219 220 it('Fails to set properties in a collection if not its onwer/administrator', async () => {221 await usingApi(async api => {222 const collection = await createCollectionExpectSuccess();223224 await expect(executeTransaction(225 api, 226 bob, 227 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 228 )).to.be.rejectedWith(/common\.NoPermission/);229 230 const properties = (await api.query.common.collectionProperties(collection)).toJSON();231 expect(properties.map).to.be.empty;232 expect(properties.consumedSpace).to.equal(0);233 });234 });235 236 it('Fails to set properties that exceed the limits', async () => {237 await usingApi(async api => {238 const collection = await createCollectionExpectSuccess();239 const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 240241 // Mute the general tx parsing error, too many bytes to process242 {243 console.error = () => {};244 await expect(executeTransaction(245 api, 246 alice, 247 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 248 )).to.be.rejected;249 }250251 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();252 expect(properties).to.be.empty;253254 await expect(executeTransaction(255 api, 256 alice, 257 api.tx.unique.setCollectionProperties(collection, [258 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 259 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 260 ]), 261 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);262263 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();264 expect(properties).to.be.empty;265 });266 });267 268 it('Fails to set more properties than it is allowed', async () => {269 await usingApi(async api => {270 const collection = await createCollectionExpectSuccess();271272 const propertiesToBeSet = [];273 for (let i = 0; i < 65; i++) {274 propertiesToBeSet.push({275 key: 'electron_' + i,276 value: Math.random() > 0.5 ? 'high' : 'low',277 });278 }279280 await expect(executeTransaction(281 api, 282 alice, 283 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 284 )).to.be.rejectedWith(/common\.PropertyLimitReached/);285286 const properties = (await api.query.common.collectionProperties(collection)).toJSON();287 expect(properties.map).to.be.empty;288 expect(properties.consumedSpace).to.equal(0);289 });290 });291292 it('Fails to set properties with invalid names', async () => {293 await usingApi(async api => {294 const collection = await createCollectionExpectSuccess();295296 const invalidProperties = [297 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],298 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],299 [{key: 'déjà vu', value: 'hmm...'}],300 ];301302 for (let i = 0; i < invalidProperties.length; i++) {303 await expect(executeTransaction(304 api, 305 alice, 306 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 307 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);308 }309310 await expect(executeTransaction(311 api, 312 alice, 313 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 314 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);315316 await expect(executeTransaction(317 api, 318 alice, 319 api.tx.unique.setCollectionProperties(collection, [320 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},321 ]), 322 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;323324 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');325326 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();327 expect(properties).to.be.deep.equal([328 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},329 ]);330331 for (let i = 0; i < invalidProperties.length; i++) {332 await expect(executeTransaction(333 api, 334 alice, 335 api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 336 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);337 }338 });339 });340});341342// ---------- ACCESS RIGHTS343344describe('Integration Test: Access Rights to Token Properties', () => {345 before(async () => {346 await usingApi(async (api, privateKeyWrapper) => {347 alice = privateKeyWrapper('//Alice');348 bob = privateKeyWrapper('//Bob');349 });350 });351 352 it('Reads access rights to properties of a collection', async () => {353 await usingApi(async api => {354 const collection = await createCollectionExpectSuccess();355 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();356 expect(propertyRights).to.be.empty;357 });358 });359 360 it('Sets access rights to properties of a collection', async () => {361 await usingApi(async api => {362 const collection = await createCollectionExpectSuccess();363364 await expect(executeTransaction(365 api, 366 alice, 367 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 368 )).to.not.be.rejected;369370 await addCollectionAdminExpectSuccess(alice, collection, bob.address);371372 await expect(executeTransaction(373 api, 374 alice, 375 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 376 )).to.not.be.rejected;377378 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();379 expect(propertyRights).to.be.deep.equal([380 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},381 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},382 ]);383 });384 });385 386 it('Changes access rights to properties of a collection', async () => {387 await usingApi(async api => {388 const collection = await createCollectionExpectSuccess();389390 await expect(executeTransaction(391 api, 392 alice, 393 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 394 )).to.not.be.rejected;395396 await expect(executeTransaction(397 api, 398 alice, 399 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 400 )).to.not.be.rejected;401402 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();403 expect(propertyRights).to.be.deep.equal([404 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},405 ]);406 });407 });408});409410describe('Negative Integration Test: Access Rights to Token Properties', () => {411 before(async () => {412 await usingApi(async (api, privateKeyWrapper) => {413 alice = privateKeyWrapper('//Alice');414 bob = privateKeyWrapper('//Bob');415 });416 });417418 it('Prevents from setting access rights to properties of a collection if not an onwer/admin', async () => {419 await usingApi(async api => {420 const collection = await createCollectionExpectSuccess();421422 await expect(executeTransaction(423 api, 424 bob, 425 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 426 )).to.be.rejectedWith(/common\.NoPermission/);427428 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();429 expect(propertyRights).to.be.empty;430 });431 });432433 it('Prevents from adding too many possible properties', async () => {434 await usingApi(async api => {435 const collection = await createCollectionExpectSuccess();436437 const constitution = [];438 for (let i = 0; i < 65; i++) {439 constitution.push({440 key: 'property_' + i,441 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},442 });443 }444445 await expect(executeTransaction(446 api, 447 alice, 448 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 449 )).to.be.rejectedWith(/common\.PropertyLimitReached/);450451 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();452 expect(propertyRights).to.be.empty;453 });454 });455456 it('Prevents access rights to be modified if constant', async () => {457 await usingApi(async api => {458 const collection = await createCollectionExpectSuccess();459460 await expect(executeTransaction(461 api, 462 alice, 463 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 464 )).to.not.be.rejected;465466 await expect(executeTransaction(467 api, 468 alice, 469 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 470 )).to.be.rejectedWith(/common\.NoPermission/);471472 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();473 expect(propertyRights).to.deep.equal([474 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},475 ]);476 });477 });478479 it('Prevents adding properties with invalid names', async () => {480 await usingApi(async api => {481 const collection = await createCollectionExpectSuccess();482483 const invalidProperties = [484 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],485 [{key: 'G#4', permission: {tokenOwner: true}}],486 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],487 ];488489 for (let i = 0; i < invalidProperties.length; i++) {490 await expect(executeTransaction(491 api, 492 alice, 493 api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 494 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);495 }496497 await expect(executeTransaction(498 api, 499 alice, 500 api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 501 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);502503 const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string504 await expect(executeTransaction(505 api, 506 alice, 507 api.tx.unique.setTokenPropertyPermissions(collection, [508 {key: correctKey, permission: {collectionAdmin: true}},509 ]), 510 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;511512 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');513514 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();515 expect(propertyRights).to.be.deep.equal([516 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},517 ]);518 });519 });520});521522// ---------- TOKEN PROPERTIES523524describe('Integration Test: Token Properties', () => {525 let collection: number;526 let token: number;527 let nestedToken: number;528 let permissions: {permission: any, signers: IKeyringPair[]}[];529530 before(async () => {531 await usingApi(async (api, privateKeyWrapper) => {532 alice = privateKeyWrapper('//Alice');533 bob = privateKeyWrapper('//Bob');534 charlie = privateKeyWrapper('//Charlie');535536 permissions = [537 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},538 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},539 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},540 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},541 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},542 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},543 ];544 });545 });546547 beforeEach(async () => {548 await usingApi(async () => {549 collection = await createCollectionExpectSuccess();550 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});551552 token = await createItemExpectSuccess(alice, collection, 'NFT');553 nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});554555 await addCollectionAdminExpectSuccess(alice, collection, bob.address);556 await transferExpectSuccess(collection, token, alice, charlie);557 });558 });559 560 it('Reads yet empty properties of a token', async () => {561 await usingApi(async api => {562 const collection = await createCollectionExpectSuccess();563 const token = await createItemExpectSuccess(alice, collection, 'NFT');564 565 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();566 expect(properties.map).to.be.empty;567 expect(properties.consumedSpace).to.be.equal(0);568569 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;570 expect(tokenData).to.be.empty;571 });572 });573574 it('Assigns properties to a token according to permissions', async () => {575 await usingApi(async api => {576 const propertyKeys: string[] = [];577 let i = 0;578 for (const permission of permissions) {579 for (const signer of permission.signers) {580 const key = i + '_' + signer.address;581 propertyKeys.push(key);582583 await expect(executeTransaction(584 api, 585 alice, 586 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 587 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;588589 await expect(executeTransaction(590 api, 591 signer, 592 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 593 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;594 }595596 i++;597 }598599 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];600 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];601 for (let i = 0; i < properties.length; i++) {602 expect(properties[i].value).to.be.equal('Serotonin increase');603 expect(tokensData[i].value).to.be.equal('Serotonin increase');604 }605 });606 });607608 it('Changes properties of a token according to permissions', async () => {609 await usingApi(async api => {610 const propertyKeys: string[] = [];611 let i = 0;612 for (const permission of permissions) {613 if (!permission.permission.mutable) continue;614 615 for (const signer of permission.signers) {616 const key = i + '_' + signer.address;617 propertyKeys.push(key);618619 await expect(executeTransaction(620 api, 621 alice, 622 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 623 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;624625 await expect(executeTransaction(626 api, 627 signer, 628 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 629 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;630631 await expect(executeTransaction(632 api, 633 signer, 634 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 635 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;636 }637638 i++;639 }640641 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];642 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];643 for (let i = 0; i < properties.length; i++) {644 expect(properties[i].value).to.be.equal('Serotonin stable');645 expect(tokensData[i].value).to.be.equal('Serotonin stable');646 }647 });648 });649650 it('Deletes properties of a token according to permissions', async () => {651 await usingApi(async api => {652 const propertyKeys: string[] = [];653 let i = 0;654655 for (const permission of permissions) {656 if (!permission.permission.mutable) continue;657 658 for (const signer of permission.signers) {659 const key = i + '_' + signer.address;660 propertyKeys.push(key);661662 await expect(executeTransaction(663 api, 664 alice, 665 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 666 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;667668 await expect(executeTransaction(669 api, 670 signer, 671 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 672 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;673674 await expect(executeTransaction(675 api, 676 signer, 677 api.tx.unique.deleteTokenProperties(collection, token, [key]), 678 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;679 }680 681 i++;682 }683684 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];685 expect(properties).to.be.empty;686 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];687 expect(tokensData).to.be.empty;688 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);689 });690 });691692 it('Assigns properties to a nested token according to permissions', async () => {693 await usingApi(async api => {694 const propertyKeys: string[] = [];695 let i = 0;696 for (const permission of permissions) {697 for (const signer of permission.signers) {698 const key = i + '_' + signer.address;699 propertyKeys.push(key);700701 await expect(executeTransaction(702 api, 703 alice, 704 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 705 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;706707 await expect(executeTransaction(708 api, 709 signer, 710 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 711 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;712 }713714 i++;715 }716717 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];718 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];719 for (let i = 0; i < properties.length; i++) {720 expect(properties[i].value).to.be.equal('Serotonin increase');721 expect(tokensData[i].value).to.be.equal('Serotonin increase');722 }723 });724 });725726 it('Changes properties of a nested token according to permissions', async () => {727 await usingApi(async api => {728 const propertyKeys: string[] = [];729 let i = 0;730 for (const permission of permissions) {731 if (!permission.permission.mutable) continue;732 733 for (const signer of permission.signers) {734 const key = i + '_' + signer.address;735 propertyKeys.push(key);736737 await expect(executeTransaction(738 api, 739 alice, 740 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 741 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;742743 await expect(executeTransaction(744 api, 745 signer, 746 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 747 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;748749 await expect(executeTransaction(750 api, 751 signer, 752 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), 753 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;754 }755756 i++;757 }758759 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];760 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];761 for (let i = 0; i < properties.length; i++) {762 expect(properties[i].value).to.be.equal('Serotonin stable');763 expect(tokensData[i].value).to.be.equal('Serotonin stable');764 }765 });766 });767768 it('Deletes properties of a nested token according to permissions', async () => {769 await usingApi(async api => {770 const propertyKeys: string[] = [];771 let i = 0;772773 for (const permission of permissions) {774 if (!permission.permission.mutable) continue;775 776 for (const signer of permission.signers) {777 const key = i + '_' + signer.address;778 propertyKeys.push(key);779780 await expect(executeTransaction(781 api, 782 alice, 783 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 784 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;785786 await expect(executeTransaction(787 api, 788 signer, 789 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 790 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;791792 await expect(executeTransaction(793 api, 794 signer, 795 api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), 796 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;797 }798 799 i++;800 }801802 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];803 expect(properties).to.be.empty;804 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[];805 expect(tokensData).to.be.empty;806 expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0);807 });808 });809});810811describe('Negative Integration Test: Token Properties', () => {812 let collection: number;813 let token: number;814 let originalSpace: number;815 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];816817 before(async () => {818 await usingApi(async (api, privateKeyWrapper) => {819 alice = privateKeyWrapper('//Alice');820 bob = privateKeyWrapper('//Bob');821 charlie = privateKeyWrapper('//Charlie');822 const dave = privateKeyWrapper('//Dave');823824 constitution = [825 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},826 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},827 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},828 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},829 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},830 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},831 ];832 });833 });834835 beforeEach(async () => {836 collection = await createCollectionExpectSuccess();837 token = await createItemExpectSuccess(alice, collection, 'NFT');838 await addCollectionAdminExpectSuccess(alice, collection, bob.address);839 await transferExpectSuccess(collection, token, alice, charlie);840 841 await usingApi(async api => {842 let i = 0;843 for (const passage of constitution) {844 const signer = passage.signers[0];845 846 await expect(executeTransaction(847 api, 848 alice, 849 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 850 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;851852 await expect(executeTransaction(853 api, 854 signer, 855 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 856 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;857858 i++;859 }860861 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;862 });863 });864865 it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {866 await usingApi(async api => {867 let i = -1;868 for (const forbiddance of constitution) {869 i++;870 if (!forbiddance.permission.mutable) continue;871872 await expect(executeTransaction(873 api, 874 forbiddance.sinner, 875 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 876 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);877878 await expect(executeTransaction(879 api, 880 forbiddance.sinner, 881 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 882 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);883 }884885 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();886 expect(properties.consumedSpace).to.be.equal(originalSpace);887 });888 });889890 it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {891 await usingApi(async api => {892 let i = -1;893 for (const permission of constitution) {894 i++;895 if (permission.permission.mutable) continue;896897 await expect(executeTransaction(898 api, 899 permission.signers[0], 900 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 901 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);902903 await expect(executeTransaction(904 api, 905 permission.signers[0], 906 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 907 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);908 }909910 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();911 expect(properties.consumedSpace).to.be.equal(originalSpace);912 });913 });914915 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {916 await usingApi(async api => {917 await expect(executeTransaction(918 api, 919 alice, 920 api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 921 ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);922 923 await expect(executeTransaction(924 api, 925 alice, 926 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 927 ), 'on setting a new non-permitted property').to.not.be.rejected;928929 await expect(executeTransaction(930 api, 931 alice, 932 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 933 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);934935 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;936 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();937 expect(properties.consumedSpace).to.be.equal(originalSpace);938 });939 });940941 it('Forbids adding too many properties to a token', async () => {942 await usingApi(async api => {943 await expect(executeTransaction(944 api, 945 alice, 946 api.tx.unique.setTokenPropertyPermissions(collection, [947 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 948 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},949 ]), 950 ), 'on setting a new non-permitted property').to.not.be.rejected;951952 // Mute the general tx parsing error953 {954 console.error = () => {};955 await expect(executeTransaction(956 api, 957 alice, 958 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 959 )).to.be.rejected;960 }961962 await expect(executeTransaction(963 api, 964 alice, 965 api.tx.unique.setTokenProperties(collection, token, [966 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 967 {key: 'young_years', value: 'neverending'.repeat(1490)},968 ]), 969 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);970971 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;972 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();973 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);974 });975 });976});