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 collection: number;620 let token: number;621 let nestedToken: number;622 let permissions: {permission: any, signers: IKeyringPair[]}[];623624 before(async () => {625 await usingApi(async (api, privateKeyWrapper) => {626 alice = privateKeyWrapper('//Alice');627 bob = privateKeyWrapper('//Bob');628 charlie = privateKeyWrapper('//Charlie');629630 permissions = [631 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},632 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},633 {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},634 {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},635 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},636 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},637 ];638 });639 });640641 beforeEach(async () => {642 await usingApi(async () => {643 collection = await createCollectionExpectSuccess();644 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});645646 token = await createItemExpectSuccess(alice, collection, 'NFT');647 nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});648649 await addCollectionAdminExpectSuccess(alice, collection, bob.address);650 await transferExpectSuccess(collection, token, alice, charlie); });651 });652 653 it('Reads yet empty properties of a token', async () => {654 await usingApi(async api => {655 const collection = await createCollectionExpectSuccess();656 const token = await createItemExpectSuccess(alice, collection, 'NFT');657 658 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();659 expect(properties.map).to.be.empty;660 expect(properties.consumedSpace).to.be.equal(0);661662 const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;663 expect(tokenData).to.be.empty;664 });665 });666667 it('Assigns properties to a token according to permissions', async () => {668 await usingApi(async api => {669 const propertyKeys: string[] = [];670 let i = 0;671 for (const permission of permissions) {672 for (const signer of permission.signers) {673 const key = i + '_' + signer.address;674 propertyKeys.push(key);675676 await expect(executeTransaction(677 api, 678 alice, 679 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 680 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;681682 await expect(executeTransaction(683 api, 684 signer, 685 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 686 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;687 }688689 i++;690 }691692 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];693 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];694 for (let i = 0; i < properties.length; i++) {695 expect(properties[i].value).to.be.equal('Serotonin increase');696 expect(tokensData[i].value).to.be.equal('Serotonin increase');697 }698 });699 });700701 it.only('Assigns properties to a token according to permissions (ReFungible)', async () => {702 await usingApi(async api => {703 const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});704 const token = await createItemExpectSuccess(alice, collection, 'ReFungible');705 await addCollectionAdminExpectSuccess(alice, collection, bob.address);706 await transferExpectSuccess(collection, token, alice, charlie, 1, 'ReFungible');707708 const propertyKeys: string[] = [];709 let i = 0;710 for (const permission of permissions) {711 for (const signer of permission.signers) {712 const key = i + '_' + signer.address;713 propertyKeys.push(key);714715 await expect(executeTransaction(716 api, 717 alice, 718 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 719 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;720721 await expect(executeTransaction(722 api, 723 signer, 724 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 725 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;726 }727728 i++;729 }730731 732 733 734 735 736 737 });738 });739740 it('Changes properties of a token according to permissions', async () => {741 await usingApi(async api => {742 const propertyKeys: string[] = [];743 let i = 0;744 for (const permission of permissions) {745 if (!permission.permission.mutable) continue;746 747 for (const signer of permission.signers) {748 const key = i + '_' + signer.address;749 propertyKeys.push(key);750751 await expect(executeTransaction(752 api, 753 alice, 754 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 755 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;756757 await expect(executeTransaction(758 api, 759 signer, 760 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 761 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;762763 await expect(executeTransaction(764 api, 765 signer, 766 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), 767 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;768 }769770 i++;771 }772773 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];774 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];775 for (let i = 0; i < properties.length; i++) {776 expect(properties[i].value).to.be.equal('Serotonin stable');777 expect(tokensData[i].value).to.be.equal('Serotonin stable');778 }779 });780 });781782 it('Deletes properties of a token according to permissions', async () => {783 await usingApi(async api => {784 const propertyKeys: string[] = [];785 let i = 0;786787 for (const permission of permissions) {788 if (!permission.permission.mutable) continue;789 790 for (const signer of permission.signers) {791 const key = i + '_' + signer.address;792 propertyKeys.push(key);793794 await expect(executeTransaction(795 api, 796 alice, 797 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 798 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;799800 await expect(executeTransaction(801 api, 802 signer, 803 api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), 804 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;805806 await expect(executeTransaction(807 api, 808 signer, 809 api.tx.unique.deleteTokenProperties(collection, token, [key]), 810 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;811 }812 813 i++;814 }815816 const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];817 expect(properties).to.be.empty;818 const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];819 expect(tokensData).to.be.empty;820 expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);821 });822 });823824 it('Assigns properties to a nested token according to permissions', async () => {825 await usingApi(async api => {826 const propertyKeys: string[] = [];827 let i = 0;828 for (const permission of permissions) {829 for (const signer of permission.signers) {830 const key = i + '_' + signer.address;831 propertyKeys.push(key);832833 await expect(executeTransaction(834 api, 835 alice, 836 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 837 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;838839 await expect(executeTransaction(840 api, 841 signer, 842 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 843 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;844 }845846 i++;847 }848849 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];850 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];851 for (let i = 0; i < properties.length; i++) {852 expect(properties[i].value).to.be.equal('Serotonin increase');853 expect(tokensData[i].value).to.be.equal('Serotonin increase');854 }855 });856 });857858 it('Changes properties of a nested token according to permissions', async () => {859 await usingApi(async api => {860 const propertyKeys: string[] = [];861 let i = 0;862 for (const permission of permissions) {863 if (!permission.permission.mutable) continue;864 865 for (const signer of permission.signers) {866 const key = i + '_' + signer.address;867 propertyKeys.push(key);868869 await expect(executeTransaction(870 api, 871 alice, 872 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 873 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;874875 await expect(executeTransaction(876 api, 877 signer, 878 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 879 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;880881 await expect(executeTransaction(882 api, 883 signer, 884 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), 885 ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;886 }887888 i++;889 }890891 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];892 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];893 for (let i = 0; i < properties.length; i++) {894 expect(properties[i].value).to.be.equal('Serotonin stable');895 expect(tokensData[i].value).to.be.equal('Serotonin stable');896 }897 });898 });899900 it('Deletes properties of a nested token according to permissions', async () => {901 await usingApi(async api => {902 const propertyKeys: string[] = [];903 let i = 0;904905 for (const permission of permissions) {906 if (!permission.permission.mutable) continue;907 908 for (const signer of permission.signers) {909 const key = i + '_' + signer.address;910 propertyKeys.push(key);911912 await expect(executeTransaction(913 api, 914 alice, 915 api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 916 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;917918 await expect(executeTransaction(919 api, 920 signer, 921 api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 922 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;923924 await expect(executeTransaction(925 api, 926 signer, 927 api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), 928 ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;929 }930 931 i++;932 }933934 const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];935 expect(properties).to.be.empty;936 const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[];937 expect(tokensData).to.be.empty;938 expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0);939 });940 });941});942943describe('Negative Integration Test: Token Properties', () => {944 let collection: number;945 let token: number;946 let originalSpace: number;947 let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];948949 before(async () => {950 await usingApi(async (api, privateKeyWrapper) => {951 alice = privateKeyWrapper('//Alice');952 bob = privateKeyWrapper('//Bob');953 charlie = privateKeyWrapper('//Charlie');954 const dave = privateKeyWrapper('//Dave');955956 constitution = [957 {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},958 {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},959 {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},960 {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},961 {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},962 {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},963 ];964 });965 });966967 beforeEach(async () => {968 collection = await createCollectionExpectSuccess();969 token = await createItemExpectSuccess(alice, collection, 'NFT');970 await addCollectionAdminExpectSuccess(alice, collection, bob.address);971 await transferExpectSuccess(collection, token, alice, charlie);972 973 await usingApi(async api => {974 let i = 0;975 for (const passage of constitution) {976 const signer = passage.signers[0];977 978 await expect(executeTransaction(979 api, 980 alice, 981 api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), 982 ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;983984 await expect(executeTransaction(985 api, 986 signer, 987 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), 988 ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;989990 i++;991 }992993 originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;994 });995 });996997 it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {998 await usingApi(async api => {999 let i = -1;1000 for (const forbiddance of constitution) {1001 i++;1002 if (!forbiddance.permission.mutable) continue;10031004 await expect(executeTransaction(1005 api, 1006 forbiddance.sinner, 1007 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1008 ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);10091010 await expect(executeTransaction(1011 api, 1012 forbiddance.sinner, 1013 api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), 1014 ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);1015 }10161017 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1018 expect(properties.consumedSpace).to.be.equal(originalSpace);1019 });1020 });10211022 it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {1023 await usingApi(async api => {1024 let i = -1;1025 for (const permission of constitution) {1026 i++;1027 if (permission.permission.mutable) continue;10281029 await expect(executeTransaction(1030 api, 1031 permission.signers[0], 1032 api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), 1033 ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);10341035 await expect(executeTransaction(1036 api, 1037 permission.signers[0], 1038 api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), 1039 ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);1040 }10411042 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1043 expect(properties.consumedSpace).to.be.equal(originalSpace);1044 });1045 });10461047 it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {1048 await usingApi(async api => {1049 await expect(executeTransaction(1050 api, 1051 alice, 1052 api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), 1053 ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/);1054 1055 await expect(executeTransaction(1056 api, 1057 alice, 1058 api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), 1059 ), 'on setting a new non-permitted property').to.not.be.rejected;10601061 await expect(executeTransaction(1062 api, 1063 alice, 1064 api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), 1065 ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);10661067 expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;1068 const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1069 expect(properties.consumedSpace).to.be.equal(originalSpace);1070 });1071 });10721073 it('Forbids adding too many properties to a token', async () => {1074 await usingApi(async api => {1075 await expect(executeTransaction(1076 api, 1077 alice, 1078 api.tx.unique.setTokenPropertyPermissions(collection, [1079 {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 1080 {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},1081 ]), 1082 ), 'on setting a new non-permitted property').to.not.be.rejected;10831084 1085 {1086 console.error = () => {};1087 await expect(executeTransaction(1088 api, 1089 alice, 1090 api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), 1091 )).to.be.rejected;1092 }10931094 await expect(executeTransaction(1095 api, 1096 alice, 1097 api.tx.unique.setTokenProperties(collection, token, [1098 {key: 'a_holy_book', value: 'word '.repeat(3277)}, 1099 {key: 'young_years', value: 'neverending'.repeat(1490)},1100 ]), 1101 )).to.be.rejectedWith(/common\.NoSpaceForProperty/);11021103 expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;1104 const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();1105 expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);1106 });1107 });1108});