1import {expect} from 'chai';2import usingApi, {executeTransaction} from '../substrate/substrate-api';3import {4 addCollectionAdminExpectSuccess,5 CollectionMode,6 createCollectionExpectSuccess,7 setCollectionPermissionsExpectSuccess,8 createItemExpectSuccess,9 getCreateCollectionResult,10 transferExpectSuccess,11} from '../util/helpers';12import {IKeyringPair} from '@polkadot/types/types';13import {tokenIdToAddress} from '../eth/util/helpers';1415let alice: IKeyringPair;16let bob: IKeyringPair;17let charlie: IKeyringPair;1819describe('Composite Properties Test', () => {20 before(async () => {21 await usingApi(async (api, privateKeyWrapper) => {22 alice = privateKeyWrapper('//Alice');23 bob = privateKeyWrapper('//Bob');24 });25 });2627 async function testMakeSureSuppliesRequired(mode: CollectionMode) {28 await usingApi(async api => {29 const collectionId = await createCollectionExpectSuccess({mode: mode});3031 const collectionOption = await api.rpc.unique.collectionById(collectionId);32 expect(collectionOption.isSome).to.be.true;33 let collection = collectionOption.unwrap();34 expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty;35 expect(collection.properties.toHuman()).to.be.empty;3637 const propertyPermissions = [38 {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},39 {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},40 ];41 await expect(executeTransaction(42 api, 43 alice, 44 api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), 45 )).to.not.be.rejected;4647 const collectionProperties = [48 {key: 'black_hole', value: 'LIGO'},49 {key: 'electron', value: 'come bond'}, 50 ];51 await expect(executeTransaction(52 api, 53 alice, 54 api.tx.unique.setCollectionProperties(collectionId, collectionProperties), 55 )).to.not.be.rejected;5657 collection = (await api.rpc.unique.collectionById(collectionId)).unwrap();58 expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);59 expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);60 });61 }6263 it('Makes sure collectionById supplies required fields for NFT', async () => {64 await testMakeSureSuppliesRequired({type: 'NFT'});65 });6667 it('Makes sure collectionById supplies required fields for ReFungible', async () => {68 await testMakeSureSuppliesRequired({type: 'ReFungible'});69 });70});71727374describe('Integration Test: Collection Properties', () => {75 before(async () => {76 await usingApi(async (api, privateKeyWrapper) => {77 alice = privateKeyWrapper('//Alice');78 bob = privateKeyWrapper('//Bob');79 });80 });8182 it('Reads properties from a collection', async () => {83 await usingApi(async api => {84 const collection = await createCollectionExpectSuccess();85 const properties = (await api.query.common.collectionProperties(collection)).toJSON();86 expect(properties.map).to.be.empty;87 expect(properties.consumedSpace).to.equal(0);88 });89 });909192 async function testSetsPropertiesForCollection(mode: string) {93 await usingApi(async api => {94 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));95 const {collectionId} = getCreateCollectionResult(events);9697 98 await expect(executeTransaction(99 api, 100 bob, 101 api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), 102 )).to.not.be.rejected;103104 await addCollectionAdminExpectSuccess(bob, collectionId, alice.address);105106 107 await expect(executeTransaction(108 api, 109 alice, 110 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 111 )).to.not.be.rejected;112113 const properties = (await api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman();114 expect(properties).to.be.deep.equal([115 {key: 'electron', value: 'come bond'},116 {key: 'black_hole', value: ''},117 ]);118 });119 }120 it('Sets properties for a NFT collection', async () => {121 await testSetsPropertiesForCollection('NFT');122 });123 it('Sets properties for a ReFungible collection', async () => {124 await testSetsPropertiesForCollection('ReFungible');125 });126127 async function testCheckValidNames(mode: string) {128 await usingApi(async api => {129 const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));130 const {collectionId} = getCreateCollectionResult(events);131 132 133 await expect(executeTransaction(134 api, 135 bob, 136 api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 137 )).to.not.be.rejected;138 139 140 await expect(executeTransaction(141 api, 142 bob, 143 api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 144 )).to.not.be.rejected;145 146 147 await expect(executeTransaction(148 api, 149 bob, 150 api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 151 )).to.not.be.rejected;152 153 154 await expect(executeTransaction(155 api, 156 bob, 157 api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 158 )).to.not.be.rejected;159 160 161 await expect(executeTransaction(162 api, 163 bob, 164 api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), 165 )).to.not.be.rejected;166 167 const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];168 const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();169 expect(properties).to.be.deep.equal([170 {key: 'alpha', value: ''},171 {key: '123', value: ''},172 {key: 'black_hole', value: ''},173 {key: 'semi-automatic', value: ''},174 {key: 'build.rs', value: ''},175 ]);176 });177 }178 it('Check valid names for NFT collection properties keys', async () => {179 await testCheckValidNames('NFT');180 });181 it('Check valid names for ReFungible collection properties keys', async () => {182 await testCheckValidNames('ReFungible');183 });184185 async function testChangesProperties(mode: CollectionMode) {186 await usingApi(async api => {187 const collection = await createCollectionExpectSuccess({mode: mode});188 189 await expect(executeTransaction(190 api, 191 alice, 192 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), 193 )).to.not.be.rejected;194 195 196 await expect(executeTransaction(197 api, 198 alice, 199 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), 200 )).to.not.be.rejected;201 202 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();203 expect(properties).to.be.deep.equal([204 {key: 'electron', value: 'bonded'},205 {key: 'black_hole', value: 'LIGO'},206 ]);207 });208 }209 it('Changes properties of a NFT collection', async () => {210 await testChangesProperties({type: 'NFT'});211 });212 it('Changes properties of a ReFungible collection', async () => {213 await testChangesProperties({type: 'ReFungible'});214 });215216 async function testDeleteProperties(mode: CollectionMode) {217 await usingApi(async api => {218 const collection = await createCollectionExpectSuccess({mode: mode});219 220 await expect(executeTransaction(221 api, 222 alice, 223 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 224 )).to.not.be.rejected;225 226 await expect(executeTransaction(227 api, 228 alice, 229 api.tx.unique.deleteCollectionProperties(collection, ['electron']), 230 )).to.not.be.rejected;231 232 const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();233 expect(properties).to.be.deep.equal([234 {key: 'black_hole', value: 'LIGO'},235 ]);236 }); 237 }238 it('Deletes properties of a NFT collection', async () => {239 await testDeleteProperties({type: 'NFT'});240 });241 it('Deletes properties of a ReFungible collection', async () => {242 await testDeleteProperties({type: 'ReFungible'});243 });244});245246describe('Negative Integration Test: Collection Properties', () => {247 before(async () => {248 await usingApi(async (api, privateKeyWrapper) => {249 alice = privateKeyWrapper('//Alice');250 bob = privateKeyWrapper('//Bob');251 });252 });253 254 async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {255 await usingApi(async api => {256 const collection = await createCollectionExpectSuccess({mode: mode});257 258 await expect(executeTransaction(259 api, 260 bob, 261 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), 262 )).to.be.rejectedWith(/common\.NoPermission/);263 264 const properties = (await api.query.common.collectionProperties(collection)).toJSON();265 expect(properties.map).to.be.empty;266 expect(properties.consumedSpace).to.equal(0);267 }); 268 }269 it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {270 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});271 });272 it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => {273 await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});274 });275 276 async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {277 await usingApi(async api => {278 const collection = await createCollectionExpectSuccess({mode: mode});279 const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; 280 281 282 {283 console.error = () => {};284 await expect(executeTransaction(285 api, 286 alice, 287 api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), 288 )).to.be.rejected;289 }290 291 let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();292 expect(properties).to.be.empty;293 294 await expect(executeTransaction(295 api, 296 alice, 297 api.tx.unique.setCollectionProperties(collection, [298 {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 299 {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 300 ]), 301 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);302 303 properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();304 expect(properties).to.be.empty;305 }); 306 }307 it('Fails to set properties that exceed the limits (NFT)', async () => {308 await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});309 });310 it('Fails to set properties that exceed the limits (ReFungible)', async () => {311 await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});312 });313 314 async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {315 await usingApi(async api => {316 const collection = await createCollectionExpectSuccess({mode: mode});317 318 const propertiesToBeSet = [];319 for (let i = 0; i < 65; i++) {320 propertiesToBeSet.push({321 key: 'electron_' + i,322 value: Math.random() > 0.5 ? 'high' : 'low',323 });324 }325 326 await expect(executeTransaction(327 api, 328 alice, 329 api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), 330 )).to.be.rejectedWith(/common\.PropertyLimitReached/);331 332 const properties = (await api.query.common.collectionProperties(collection)).toJSON();333 expect(properties.map).to.be.empty;334 expect(properties.consumedSpace).to.equal(0);335 }); 336 }337 it('Fails to set more properties than it is allowed (NFT)', async () => {338 await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});339 });340 it('Fails to set more properties than it is allowed (ReFungible)', async () => {341 await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});342 });343 344 async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {345 await usingApi(async api => {346 const collection = await createCollectionExpectSuccess({mode: mode});347 348 const invalidProperties = [349 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],350 [{key: 'Mr/Sandman', value: 'Bring me a gene'}],351 [{key: 'déjà vu', value: 'hmm...'}],352 ];353 354 for (let i = 0; i < invalidProperties.length; i++) {355 await expect(executeTransaction(356 api, 357 alice, 358 api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), 359 ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);360 }361 362 await expect(executeTransaction(363 api, 364 alice, 365 api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), 366 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);367 368 await expect(executeTransaction(369 api, 370 alice, 371 api.tx.unique.setCollectionProperties(collection, [372 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},373 ]), 374 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;375 376 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');377 378 const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();379 expect(properties).to.be.deep.equal([380 {key: 'CRISPR-Cas9', value: 'rewriting nature!'},381 ]);382 383 for (let i = 0; i < invalidProperties.length; i++) {384 await expect(executeTransaction(385 api, 386 alice, 387 api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), 388 ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);389 }390 });391 }392 it('Fails to set properties with invalid names (NFT)', async () => {393 await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});394 });395 it('Fails to set properties with invalid names (ReFungible)', async () => {396 await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});397 });398});399400401402describe('Integration Test: Access Rights to Token Properties', () => {403 before(async () => {404 await usingApi(async (api, privateKeyWrapper) => {405 alice = privateKeyWrapper('//Alice');406 bob = privateKeyWrapper('//Bob');407 });408 });409 410 it('Reads access rights to properties of a collection', async () => {411 await usingApi(async api => {412 const collection = await createCollectionExpectSuccess();413 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();414 expect(propertyRights).to.be.empty;415 });416 });417 418 async function testSetsAccessRightsToProperties(mode: CollectionMode) {419 await usingApi(async api => {420 const collection = await createCollectionExpectSuccess({mode: mode});421 422 await expect(executeTransaction(423 api, 424 alice, 425 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), 426 )).to.not.be.rejected;427 428 await addCollectionAdminExpectSuccess(alice, collection, bob.address);429 430 await expect(executeTransaction(431 api, 432 alice, 433 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), 434 )).to.not.be.rejected;435 436 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();437 expect(propertyRights).to.be.deep.equal([438 {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},439 {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},440 ]);441 }); 442 }443 it('Sets access rights to properties of a collection (NFT)', async () => {444 await testSetsAccessRightsToProperties({type: 'NFT'});445 });446 it('Sets access rights to properties of a collection (ReFungible)', async () => {447 await testSetsAccessRightsToProperties({type: 'ReFungible'});448 });449 450 async function testChangesAccessRightsToProperty(mode: CollectionMode) {451 await usingApi(async api => {452 const collection = await createCollectionExpectSuccess({mode: mode});453 454 await expect(executeTransaction(455 api, 456 alice, 457 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), 458 )).to.not.be.rejected;459 460 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;465 466 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();467 expect(propertyRights).to.be.deep.equal([468 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},469 ]);470 });471 }472 it('Changes access rights to properties of a NFT collection', async () => {473 await testChangesAccessRightsToProperty({type: 'NFT'});474 });475 it('Changes access rights to properties of a ReFungible collection', async () => {476 await testChangesAccessRightsToProperty({type: 'ReFungible'});477 });478});479480describe('Negative Integration Test: Access Rights to Token Properties', () => {481 before(async () => {482 await usingApi(async (api, privateKeyWrapper) => {483 alice = privateKeyWrapper('//Alice');484 bob = privateKeyWrapper('//Bob');485 });486 });487488 async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {489 await usingApi(async api => {490 const collection = await createCollectionExpectSuccess({mode: mode});491 492 await expect(executeTransaction(493 api, 494 bob, 495 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), 496 )).to.be.rejectedWith(/common\.NoPermission/);497 498 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();499 expect(propertyRights).to.be.empty;500 });501 }502 it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {503 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});504 });505 it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => {506 await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});507 });508509 async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {510 await usingApi(async api => {511 const collection = await createCollectionExpectSuccess({mode: mode});512 513 const constitution = [];514 for (let i = 0; i < 65; i++) {515 constitution.push({516 key: 'property_' + i,517 permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},518 });519 }520 521 await expect(executeTransaction(522 api, 523 alice, 524 api.tx.unique.setTokenPropertyPermissions(collection, constitution), 525 )).to.be.rejectedWith(/common\.PropertyLimitReached/);526 527 const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();528 expect(propertyRights).to.be.empty;529 }); 530 }531 it('Prevents from adding too many possible properties (NFT)', async () => {532 await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});533 });534 it('Prevents from adding too many possible properties (ReFungible)', async () => {535 await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});536 });537538 async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {539 await usingApi(async api => {540 const collection = await createCollectionExpectSuccess({mode: mode});541 542 await expect(executeTransaction(543 api, 544 alice, 545 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), 546 )).to.not.be.rejected;547 548 await expect(executeTransaction(549 api, 550 alice, 551 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), 552 )).to.be.rejectedWith(/common\.NoPermission/);553 554 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();555 expect(propertyRights).to.deep.equal([556 {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},557 ]);558 }); 559 }560 it('Prevents access rights to be modified if constant (NFT)', async () => {561 await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});562 });563 it('Prevents access rights to be modified if constant (ReFungible)', async () => {564 await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});565 });566567 async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {568 await usingApi(async api => {569 const collection = await createCollectionExpectSuccess({mode: mode});570 571 const invalidProperties = [572 [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],573 [{key: 'G#4', permission: {tokenOwner: true}}],574 [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],575 ];576 577 for (let i = 0; i < invalidProperties.length; i++) {578 await expect(executeTransaction(579 api, 580 alice, 581 api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), 582 ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);583 }584 585 await expect(executeTransaction(586 api, 587 alice, 588 api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]), 589 ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);590 591 const correctKey = '--0x03116e387820CA05'; 592 await expect(executeTransaction(593 api, 594 alice, 595 api.tx.unique.setTokenPropertyPermissions(collection, [596 {key: correctKey, permission: {collectionAdmin: true}},597 ]), 598 ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;599 600 const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');601 602 const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();603 expect(propertyRights).to.be.deep.equal([604 {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},605 ]);606 });607 }608 it('Prevents adding properties with invalid names (NFT)', async () => {609 await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});610 });611 it('Prevents adding properties with invalid names (ReFungible)', async () => {612 await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});613 });614});615616617618describe('Integration Test: Token Properties', () => {619 let permissions: {permission: any, signers: IKeyringPair[]}[];620621 before(async () => {622 await usingApi(async (api, privateKeyWrapper) => {623 alice = privateKeyWrapper('//Alice'); 624 bob = privateKeyWrapper('//Bob'); 625 charlie = privateKeyWrapper('//Charlie'); 626627 permissions = [628 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},629 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},630 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},631 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},632 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},633 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},634 ];635 });636 });637 638 async function testReadsYetEmptyProperties(mode: CollectionMode) {639 await usingApi(async api => {640 const collection = await createCollectionExpectSuccess({mode: mode});641 const token = await createItemExpectSuccess(alice, collection, mode.type);642 643 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();644 expect(properties.map).to.be.empty;645 expect(properties.consumedSpace).to.be.equal(0);646 647 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;648 expect(tokenData).to.be.empty;649 });650 }651 it('Reads yet empty properties of a token (NFT)', async () => {652 await testReadsYetEmptyProperties({type: 'NFT'});653 });654 it('Reads yet empty properties of a token (ReFungible)', async () => {655 await testReadsYetEmptyProperties({type: 'ReFungible'});656 });657658 async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {659 await usingApi(async api => {660 const collection = await createCollectionExpectSuccess({mode: mode});661 const token = await createItemExpectSuccess(alice, collection, mode.type);662 await addCollectionAdminExpectSuccess(alice, collection, bob.address);663 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);664665 const propertyKeys: string[] = [];666 let i = 0;667 for (const permission of permissions) {668 for (const signer of permission.signers) {669 const key = i + '_' + signer.address;670 propertyKeys.push(key);671672 await expect(executeTransaction(673 api, 674 alice, 675 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 676 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;677678 await expect(executeTransaction(679 api, 680 signer, 681 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 682 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;683 }684685 i++;686 }687688 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];689 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];690 for (let i = 0; i < properties.length; i++) {691 expect(properties[i].value).to.be.equal('Serotonin increase');692 expect(tokensData[i].value).to.be.equal('Serotonin increase');693 }694 });695 }696 it('Assigns properties to a token according to permissions (NFT)', async () => {697 await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);698 });699 it('Assigns properties to a token according to permissions (ReFungible)', async () => {700 await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);701 });702703 async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {704 await usingApi(async api => {705 const collection = await createCollectionExpectSuccess({mode: mode});706 const token = await createItemExpectSuccess(alice, collection, mode.type);707 await addCollectionAdminExpectSuccess(alice, collection, bob.address);708 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);709710 const propertyKeys: string[] = [];711 let i = 0;712 for (const permission of permissions) {713 if (!permission.permission.mutable) continue;714 715 for (const signer of permission.signers) {716 const key = i + '_' + signer.address;717 propertyKeys.push(key);718 719 await expect(executeTransaction(720 api, 721 alice, 722 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 723 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;724 725 await expect(executeTransaction(726 api, 727 signer, 728 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 729 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;730 731 await expect(executeTransaction(732 api, 733 signer, 734 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 735 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;736 }737 738 i++;739 }740 741 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];742 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];743 for (let i = 0; i < properties.length; i++) {744 expect(properties[i].value).to.be.equal('Serotonin stable');745 expect(tokensData[i].value).to.be.equal('Serotonin stable');746 }747 });748 }749 it('Changes properties of a token according to permissions (NFT)', async () => {750 await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);751 });752 it('Changes properties of a token according to permissions (ReFungible)', async () => {753 await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);754 });755756 async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {757 await usingApi(async api => {758 const collection = await createCollectionExpectSuccess({mode: mode});759 const token = await createItemExpectSuccess(alice, collection, mode.type);760 await addCollectionAdminExpectSuccess(alice, collection, bob.address);761 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);762763 const propertyKeys: string[] = [];764 let i = 0;765 766 for (const permission of permissions) {767 if (!permission.permission.mutable) continue;768 769 for (const signer of permission.signers) {770 const key = i + '_' + signer.address;771 propertyKeys.push(key);772 773 await expect(executeTransaction(774 api, 775 alice, 776 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 777 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;778 779 await expect(executeTransaction(780 api, 781 signer, 782 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 783 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;784 785 await expect(executeTransaction(786 api, 787 signer, 788 api.tx.unique.deleteTokenProperties(collection, token, [key]), 789 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;790 }791 792 i++;793 }794 795 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];796 expect(properties).to.be.empty;797 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];798 expect(tokensData).to.be.empty;799 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);800 });801 }802 it('Deletes properties of a token according to permissions (NFT)', async () => {803 await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);804 });805 it('Deletes properties of a token according to permissions (ReFungible)', async () => {806 await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);807 });808809 810 811 812 813 814 815 816 817818 819 820 821 822 823824 825 826 827 828 829 830831 832 833834 835 836 837 838 839 840 841 842843 844 845 846 847 848 849 850 851 852 853854 855 856 857 858 859860 861 862 863 864 865866 867 868 869 870 871 872873 874 875876 877 878 879 880 881 882 883 884885 886 887 888 889890 891 892 893 894 895 896897 898 899 900 901 902903 904 905 906 907 908909 910 911 912 913 914 915 916 917 918919 920 921 922 923 924 925 926});927928describe('Negative Integration Test: Token Properties', () => {929 let collection: number;930 let token: number;931 let originalSpace: number;932 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];933934 before(async () => {935 await usingApi(async (api, privateKeyWrapper) => {936 alice = privateKeyWrapper('//Alice');937 bob = privateKeyWrapper('//Bob');938 charlie = privateKeyWrapper('//Charlie');939 const dave = privateKeyWrapper('//Dave');940941 constitution = [942 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},943 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},944 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},945 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},946 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},947 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},948 ];949 });950 });951952 async function prepare(mode: CollectionMode, pieces: number) {953 collection = await createCollectionExpectSuccess({mode: mode});954 token = await createItemExpectSuccess(alice, collection, mode.type);955 await addCollectionAdminExpectSuccess(alice, collection, bob.address);956 await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);957 958 await usingApi(async api => {959 let i = 0;960 for (const passage of constitution) {961 const signer = passage.signers[0];962 963 await expect(executeTransaction(964 api, 965 alice, 966 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 967 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;968 969 await expect(executeTransaction(970 api, 971 signer, 972 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 973 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;974 975 i++;976 }977 978 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;979 }); 980 }981982 async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {983 await prepare(mode, pieces);984 985 await usingApi(async api => {986 let i = -1;987 for (const forbiddance of constitution) {988 i++;989 if (!forbiddance.permission.mutable) continue;990 991 await expect(executeTransaction(992 api, 993 forbiddance.sinner, 994 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 995 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);996 997 await expect(executeTransaction(998 api, 999 forbiddance.sinner, 1000 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 1001 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);1002 }1003 1004 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1005 expect(properties.consumedSpace).to.be.equal(originalSpace);1006 });1007 }1008 it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {1009 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);1010 });1011 it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => {1012 await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);1013 });10141015 async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {1016 await prepare(mode, pieces);1017 1018 await usingApi(async api => {1019 let i = -1;1020 for (const permission of constitution) {1021 i++;1022 if (permission.permission.mutable) continue;1023 1024 await expect(executeTransaction(1025 api, 1026 permission.signers[0], 1027 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1028 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1029 1030 await expect(executeTransaction(1031 api, 1032 permission.signers[0], 1033 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 1034 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1035 }1036 1037 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1038 expect(properties.consumedSpace).to.be.equal(originalSpace);1039 }); 1040 }1041 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {1042 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);1043 });1044 it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => {1045 await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);1046 });10471048 async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {1049 await prepare(mode, pieces);10501051 await usingApi(async api => {1052 await expect(executeTransaction(1053 api, 1054 alice, 1055 api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 1056 ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);1057 1058 await expect(executeTransaction(1059 api, 1060 alice, 1061 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 1062 ), 'on setting a new non-permitted property').to.not.be.rejected;1063 1064 await expect(executeTransaction(1065 api, 1066 alice, 1067 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 1068 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);1069 1070 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;1071 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1072 expect(properties.consumedSpace).to.be.equal(originalSpace);1073 });1074 }1075 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {1076 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);1077 });1078 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => {1079 await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);1080 });10811082 async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {1083 await prepare(mode, pieces);10841085 await usingApi(async api => {1086 await expect(executeTransaction(1087 api, 1088 alice, 1089 api.tx.unique.setTokenPropertyPermissions(collection, [1090 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 1091 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},1092 ]), 1093 ), 'on setting a new non-permitted property').to.not.be.rejected;1094 1095 1096 {1097 console.error = () => {};1098 await expect(executeTransaction(1099 api, 1100 alice, 1101 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 1102 )).to.be.rejected;1103 }1104 1105 await expect(executeTransaction(1106 api, 1107 alice, 1108 api.tx.unique.setTokenProperties(collection, token, [1109 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 1110 {key: 'young_years', value: 'neverending'.repeat(1490)},1111 ]), 1112 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);1113 1114 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;1115 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1116 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);1117 });1118 }1119 it('Forbids adding too many properties to a token (NFT)', async () => {1120 await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);1121 });1122 it('Forbids adding too many properties to a token (ReFungible)', async () => {1123 await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);1124 });1125});11261127describe('ReFungible token properties permissions tests', () => {1128 let collection: number;1129 let token: number;11301131 before(async () => {1132 await usingApi(async (api, privateKeyWrapper) => {1133 alice = privateKeyWrapper('//Alice');1134 bob = privateKeyWrapper('//Bob');1135 charlie = privateKeyWrapper('//Charlie');1136 });1137 });11381139 beforeEach(async () => {1140 await usingApi(async api => {1141 collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});1142 token = await createItemExpectSuccess(alice, collection, 'ReFungible');1143 await addCollectionAdminExpectSuccess(alice, collection, bob.address);11441145 await expect(executeTransaction(1146 api, 1147 alice, 1148 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1149 )).to.not.be.rejected;1150 });1151 });11521153 it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {1154 await usingApi(async api => {1155 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1156 1157 await expect(executeTransaction(1158 api, 1159 alice, 1160 api.tx.unique.setTokenProperties(collection, token, [1161 {key: 'key', value: 'word'}, 1162 ]), 1163 )).to.be.rejectedWith(/common\.NoPermission/);1164 });1165 });11661167 it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {1168 await usingApi(async api => {1169 await expect(executeTransaction(1170 api, 1171 alice, 1172 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), 1173 )).to.not.be.rejected;1174 1175 await expect(executeTransaction(1176 api, 1177 alice, 1178 api.tx.unique.setTokenProperties(collection, token, [1179 {key: 'key', value: 'word'}, 1180 ]), 1181 )).to.be.not.rejected;11821183 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1184 1185 await expect(executeTransaction(1186 api, 1187 alice, 1188 api.tx.unique.setTokenProperties(collection, token, [1189 {key: 'key', value: 'bad word'}, 1190 ]), 1191 )).to.be.rejectedWith(/common\.NoPermission/);1192 });1193 });11941195 it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {1196 await usingApi(async api => {1197 await expect(executeTransaction(1198 api, 1199 alice, 1200 api.tx.unique.setTokenProperties(collection, token, [1201 {key: 'key', value: 'word'}, 1202 ]), 1203 )).to.be.not.rejected;12041205 await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');1206 1207 await expect(executeTransaction(1208 api, 1209 alice, 1210 api.tx.unique.deleteTokenProperties(collection, token, [1211 'key',1212 ]), 1213 )).to.be.rejectedWith(/common\.NoPermission/);1214 });1215 });1216});