git.delta.rocks / unique-network / refs/commits / 77a7be634bdd

difftreelog

CORE-410 Adapt rest token property test for ReFungible collection

Trubnikov Sergey2022-07-01parent: #c5c3942.patch.diff
in: master

1 file changed

modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
before · tests/src/nesting/properties.test.ts
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});7172// ---------- COLLECTION PROPERTIES7374describe('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      // As owner98      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      // As administrator107      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      // alpha symbols133      await expect(executeTransaction(134        api, 135        bob, 136        api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 137      )).to.not.be.rejected;138  139      // numeric symbols140      await expect(executeTransaction(141        api, 142        bob, 143        api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 144      )).to.not.be.rejected;145  146      // underscore symbol147      await expect(executeTransaction(148        api, 149        bob, 150        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 151      )).to.not.be.rejected;152  153      // dash symbol154      await expect(executeTransaction(155        api, 156        bob, 157        api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 158      )).to.not.be.rejected;159  160      // underscore symbol161      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      // Mutate the properties196      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      // Mute the general tx parsing error, too many bytes to process282      {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});399400// ---------- ACCESS RIGHTS401402describe('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'; // PolkadotJS would parse this as an already encoded hex-string592      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});615616// ---------- TOKEN PROPERTIES617618describe('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      // const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];732      // const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];733      // for (let i = 0; i < properties.length; i++) {734      //   expect(properties[i].value).to.be.equal('Serotonin increase');735      //   expect(tokensData[i].value).to.be.equal('Serotonin increase');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      // Mute the general tx parsing error1085      {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});
after · tests/src/nesting/properties.test.ts
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});7172// ---------- COLLECTION PROPERTIES7374describe('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      // As owner98      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      // As administrator107      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      // alpha symbols133      await expect(executeTransaction(134        api, 135        bob, 136        api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), 137      )).to.not.be.rejected;138  139      // numeric symbols140      await expect(executeTransaction(141        api, 142        bob, 143        api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), 144      )).to.not.be.rejected;145  146      // underscore symbol147      await expect(executeTransaction(148        api, 149        bob, 150        api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), 151      )).to.not.be.rejected;152  153      // dash symbol154      await expect(executeTransaction(155        api, 156        bob, 157        api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), 158      )).to.not.be.rejected;159  160      // underscore symbol161      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      // Mutate the properties196      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      // Mute the general tx parsing error, too many bytes to process282      {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});399400// ---------- ACCESS RIGHTS401402describe('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'; // PolkadotJS would parse this as an already encoded hex-string592      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});615616// ---------- TOKEN PROPERTIES617618describe('Integration Test: Token Properties', () => {619  let permissions: {permission: any, signers: IKeyringPair[]}[];620621  before(async () => {622    await usingApi(async (api, privateKeyWrapper) => {623      alice = privateKeyWrapper('//Alice'); // collection owner624      bob = privateKeyWrapper('//Bob'); // collection admin625      charlie = privateKeyWrapper('//Charlie'); // token owner626627      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  it('Assigns properties to a nested token according to permissions', async () => {810    await usingApi(async api => {811      const propertyKeys: string[] = [];812      let i = 0;813      for (const permission of permissions) {814        for (const signer of permission.signers) {815          const key = i + '_' + signer.address;816          propertyKeys.push(key);817818          await expect(executeTransaction(819            api, 820            alice, 821            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 822          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;823824          await expect(executeTransaction(825            api, 826            signer, 827            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 828          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;829        }830831        i++;832      }833834      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];835      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];836      for (let i = 0; i < properties.length; i++) {837        expect(properties[i].value).to.be.equal('Serotonin increase');838        expect(tokensData[i].value).to.be.equal('Serotonin increase');839      }840    });841  });842843  it('Changes properties of a nested token according to permissions', async () => {844    await usingApi(async api => {845      const propertyKeys: string[] = [];846      let i = 0;847      for (const permission of permissions) {848        if (!permission.permission.mutable) continue;849        850        for (const signer of permission.signers) {851          const key = i + '_' + signer.address;852          propertyKeys.push(key);853854          await expect(executeTransaction(855            api, 856            alice, 857            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 858          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;859860          await expect(executeTransaction(861            api, 862            signer, 863            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 864          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;865866          await expect(executeTransaction(867            api, 868            signer, 869            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), 870          ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;871        }872873        i++;874      }875876      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[];877      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[];878      for (let i = 0; i < properties.length; i++) {879        expect(properties[i].value).to.be.equal('Serotonin stable');880        expect(tokensData[i].value).to.be.equal('Serotonin stable');881      }882    });883  });884885  it('Deletes properties of a nested token according to permissions', async () => {886    await usingApi(async api => {887      const propertyKeys: string[] = [];888      let i = 0;889890      for (const permission of permissions) {891        if (!permission.permission.mutable) continue;892        893        for (const signer of permission.signers) {894          const key = i + '_' + signer.address;895          propertyKeys.push(key);896897          await expect(executeTransaction(898            api, 899            alice, 900            api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), 901          ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;902903          await expect(executeTransaction(904            api, 905            signer, 906            api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), 907          ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;908909          await expect(executeTransaction(910            api, 911            signer, 912            api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), 913          ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected;914        }915        916        i++;917      }918919      const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[];920      expect(properties).to.be.empty;921      const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[];922      expect(tokensData).to.be.empty;923      expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0);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      // Mute the general tx parsing error1096      {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});