git.delta.rocks / unique-network / refs/commits / 04cd15cc5d24

difftreelog

source

tests/src/nesting/properties.test.ts44.4 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617/*import usingApi, {executeTransaction} from '../substrate/substrate-api';18import {19  addCollectionAdminExpectSuccess,20  CollectionMode,21  createCollectionExpectSuccess,22  setCollectionPermissionsExpectSuccess,23  createItemExpectSuccess,24  getCreateCollectionResult,25  transferExpectSuccess,26} from '../util/helpers';*/27import {IKeyringPair} from '@polkadot/types/types';28import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';29import {UniqueCollectionBase, UniqueHelper, UniqueNFTCollection, UniqueNFTToken, UniqueRFTCollection, UniqueRFTToken} from '../util/playgrounds/unique';3031// ---------- COLLECTION PROPERTIES3233describe('Integration Test: Collection Properties', () => {34  let alice: IKeyringPair;35  let bob: IKeyringPair;3637  before(async () => {38    await usingPlaygrounds(async (helper, privateKey) => {39      const donor = privateKey('//Alice');40      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);41    });42  });4344  itSub('Properties are initially empty', async ({helper}) => {45    const collection = await helper.nft.mintCollection(alice);46    expect(await collection.getProperties()).to.be.empty;47  });4849  async function testSetsPropertiesForCollection(collection: UniqueCollectionBase) {50    // As owner51    await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;5253    await collection.addAdmin(alice, {Substrate: bob.address});5455    // As administrator56    await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;5758    const properties = await collection.getProperties();59    expect(properties).to.include.deep.members([60      {key: 'electron', value: 'come bond'},61      {key: 'black_hole', value: ''},62    ]);63  }6465  itSub('Sets properties for a NFT collection', async ({helper}) =>  {66    await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));67  });6869  itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {70    await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));71  });7273  async function testCheckValidNames(collection: UniqueCollectionBase) {74    // alpha symbols75    await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;7677    // numeric symbols78    await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;7980    // underscore symbol81    await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;8283    // dash symbol84    await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;8586    // dot symbol87    await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;8889    const properties = await collection.getProperties();90    expect(properties).to.include.deep.members([91      {key: 'answer', value: ''},92      {key: '451', value: ''},93      {key: 'black_hole', value: ''},94      {key: '-', value: ''},95      {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},96    ]);97  }9899  itSub('Check valid names for NFT collection properties keys', async ({helper}) =>  {100    await testCheckValidNames(await helper.nft.mintCollection(alice));101  });102103  itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {104    await testCheckValidNames(await helper.rft.mintCollection(alice));105  });106107  async function testChangesProperties(collection: UniqueCollectionBase) {108    await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;109110    // Mutate the properties111    await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;112113    const properties = await collection.getProperties();114    expect(properties).to.include.deep.members([115      {key: 'electron', value: 'come bond'},116      {key: 'black_hole', value: 'LIGO'},117    ]);118  }119120  itSub('Changes properties of a NFT collection', async ({helper}) =>  {121    await testChangesProperties(await helper.nft.mintCollection(alice));122  });123124  itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {125    await testChangesProperties(await helper.rft.mintCollection(alice));126  });127128  async function testDeleteProperties(collection: UniqueCollectionBase) {129    await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;130131    await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;132133    const properties = await collection.getProperties(['black_hole', 'electron']);134    expect(properties).to.be.deep.equal([135      {key: 'black_hole', value: 'LIGO'},136    ]);137  }138139  itSub('Deletes properties of a NFT collection', async ({helper}) =>  {140    await testDeleteProperties(await helper.nft.mintCollection(alice));141  });142143  itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {144    await testDeleteProperties(await helper.rft.mintCollection(alice));145  });146});147148describe('Negative Integration Test: Collection Properties', () => {149  let alice: IKeyringPair;150  let bob: IKeyringPair;151152  before(async () => {153    await usingPlaygrounds(async (helper, privateKey) => {154      const donor = privateKey('//Alice');155      [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);156    });157  });158  159  async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueCollectionBase) {  160    await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))161      .to.be.rejectedWith(/common\.NoPermission/);162163    expect(await collection.getProperties()).to.be.empty;164  }165166  itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) =>  {167    await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));168  });169170  itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {171    await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));172  });173  174  async function testFailsSetPropertiesThatExeedLimits(collection: UniqueCollectionBase) {175    const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();176  177    // Mute the general tx parsing error, too many bytes to process178    {179      console.error = () => {};180      await expect(collection.setProperties(alice, [181        {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},182      ])).to.be.rejected;183    }184185    expect(await collection.getProperties(['electron'])).to.be.empty;186187    await expect(collection.setProperties(alice, [188      {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 189      {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 190    ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);191192    expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;193  }194195  itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) =>  {196    await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));197  });198199  itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {200    await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));201  });202  203  async function testFailsSetMorePropertiesThanAllowed(collection: UniqueCollectionBase) {204    const propertiesToBeSet = [];205    for (let i = 0; i < 65; i++) {206      propertiesToBeSet.push({207        key: 'electron_' + i,208        value: Math.random() > 0.5 ? 'high' : 'low',209      });210    }211212    await expect(collection.setProperties(alice, propertiesToBeSet)).213      to.be.rejectedWith(/common\.PropertyLimitReached/);214215    expect(await collection.getProperties()).to.be.empty;216  }217218  itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) =>  {219    await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));220  });221222  itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {223    await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));224  });225  226  async function testFailsSetPropertiesWithInvalidNames(collection: UniqueCollectionBase) {227    const invalidProperties = [228      [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],229      [{key: 'Mr/Sandman', value: 'Bring me a gene'}],230      [{key: 'déjà vu', value: 'hmm...'}],231    ];232233    for (let i = 0; i < invalidProperties.length; i++) {234      await expect(235        collection.setProperties(alice, invalidProperties[i]), 236        `on rejecting the new badly-named property #${i}`,237      ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);238    }239240    await expect(241      collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 242      'on rejecting an unnamed property',243    ).to.be.rejectedWith(/common\.EmptyPropertyKey/);244245    await expect(246      collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 247      'on setting the correctly-but-still-badly-named property',248    ).to.be.fulfilled;249250    const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');251252    const properties = await collection.getProperties(keys);253    expect(properties).to.be.deep.equal([254      {key: 'CRISPR-Cas9', value: 'rewriting nature!'},255    ]);256257    for (let i = 0; i < invalidProperties.length; i++) {258      await expect(259        collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), 260        `on trying to delete the non-existent badly-named property #${i}`,261      ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);262    }263  }264265  itSub('Fails to set properties with invalid names (NFT)', async ({helper}) =>  {266    await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));267  });268269  itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {270    await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));271  });272});273274// ---------- ACCESS RIGHTS275276describe('Integration Test: Access Rights to Token Properties', () => {277  let alice: IKeyringPair;278  let bob: IKeyringPair;279280  before(async () => {281    await usingPlaygrounds(async (helper, privateKey) => {282      const donor = privateKey('//Alice');283      [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);284    });285  });286  287  itSub('Reads access rights to properties of a collection', async ({helper}) =>  {288    const collection = await helper.nft.mintCollection(alice);289    const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON();290    expect(propertyRights).to.be.empty;291  });292  293  async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) {  294    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}]))295      .to.be.fulfilled;296297    await collection.addAdmin(alice, {Substrate: bob.address});298299    await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]))300      .to.be.fulfilled;301302    const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);303    expect(propertyRights).to.include.deep.members([304      {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},305      {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},306    ]);307  }308309  itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) =>  {310    await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));311  });312313  itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {314    await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));315  });316  317  async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) {318    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]))319      .to.be.fulfilled;320321    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))322      .to.be.fulfilled;323324    const propertyRights = await collection.getPropertyPermissions();325    expect(propertyRights).to.be.deep.equal([326      {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},327    ]);328  }329330  itSub('Changes access rights to properties of a NFT collection', async ({helper}) =>  {331    await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));332  });333334  itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {335    await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));336  });337});338339describe('Negative Integration Test: Access Rights to Token Properties', () => {340  let alice: IKeyringPair;341  let bob: IKeyringPair;342343  before(async () => {344    await usingPlaygrounds(async (helper, privateKey) => {345      const donor = privateKey('//Alice');346      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);347    });348  });349350  async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) {351    await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]))352      .to.be.rejectedWith(/common\.NoPermission/);353354    const propertyRights = await collection.getPropertyPermissions(['skullduggery']);355    expect(propertyRights).to.be.empty;356  }357358  itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) =>  {359    await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));360  });361362  itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {363    await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));364  });365366  async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) {  367    const constitution = [];368    for (let i = 0; i < 65; i++) {369      constitution.push({370        key: 'property_' + i,371        permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},372      });373    }374375    await expect(collection.setTokenPropertyPermissions(alice, constitution))376      .to.be.rejectedWith(/common\.PropertyLimitReached/);377378    const propertyRights = await collection.getPropertyPermissions();379    expect(propertyRights).to.be.empty;380  }381382  itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) =>  {383    await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));384  });385386  itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {387    await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));388  });389390  async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) {391    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))392      .to.be.fulfilled;393394    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}]))395      .to.be.rejectedWith(/common\.NoPermission/);396397    const propertyRights = await collection.getPropertyPermissions(['skullduggery']);398    expect(propertyRights).to.deep.equal([399      {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},400    ]);401  }402403  itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) =>  {404    await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));405  });406407  itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {408    await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));409  });410411  async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) {412    const invalidProperties = [413      [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],414      [{key: 'G#4', permission: {tokenOwner: true}}],415      [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],416    ];417418    for (let i = 0; i < invalidProperties.length; i++) {419      await expect(420        collection.setTokenPropertyPermissions(alice, invalidProperties[i]), 421        `on setting the new badly-named property #${i}`,422      ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);423    }424425    await expect(426      collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 427      'on rejecting an unnamed property',428    ).to.be.rejectedWith(/common\.EmptyPropertyKey/);429430    const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string431    await expect(432      collection.setTokenPropertyPermissions(alice, [433        {key: correctKey, permission: {collectionAdmin: true}},434      ]), 435      'on setting the correctly-but-still-badly-named property',436    ).to.be.fulfilled;437438    const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');439440    const propertyRights = await collection.getPropertyPermissions(keys);441    expect(propertyRights).to.be.deep.equal([442      {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},443    ]);444  }445446  itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) =>  {447    await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));448  });449450  itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {451    await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));452  });453});454455// ---------- TOKEN PROPERTIES456457describe('Integration Test: Token Properties', () => {458  let alice: IKeyringPair; // collection owner459  let bob: IKeyringPair; // collection admin460  let charlie: IKeyringPair; // token owner461462  let permissions: {permission: any, signers: IKeyringPair[]}[];463464  before(async () => {465    await usingPlaygrounds(async (helper, privateKey) => {466      const donor = privateKey('//Alice');467      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);468    });469470    // todo:playgrounds probably separate these tests later471    permissions = [472      {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},473      {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},474      {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},475      {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},476      {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},477      {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},478    ];479  });480  481  async function testReadsYetEmptyProperties(token: UniqueNFTToken | UniqueRFTToken) {482    const properties = await token.getProperties();483    expect(properties).to.be.empty;484485    const tokenData = await token.getData();486    expect(tokenData!.properties).to.be.empty;487  }488489  itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {490    const collection = await helper.nft.mintCollection(alice);491    const token = await collection.mintToken(alice);492    await testReadsYetEmptyProperties(token);493  });494495  itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {496    const collection = await helper.rft.mintCollection(alice);497    const token = await collection.mintToken(alice);498    await testReadsYetEmptyProperties(token);499  });500501  async function testAssignPropertiesAccordingToPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {502    await token.collection.addAdmin(alice, {Substrate: bob.address});503    await token.transfer(alice, {Substrate: charlie.address}, pieces);504505    const propertyKeys: string[] = [];506    let i = 0;507    for (const permission of permissions) {508      i++;509      let j = 0;510      for (const signer of permission.signers) {511        j++;512        const key = i + '_' + signer.address;513        propertyKeys.push(key);514515        await expect(516          token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 517          `on setting permission #${i} by alice`,518        ).to.be.fulfilled;519520        await expect(521          token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), 522          `on adding property #${i} by signer #${j}`,523        ).to.be.fulfilled;524      }525    }526527    const properties = await token.getProperties(propertyKeys);528    const tokenData = await token.getData();529    for (let i = 0; i < properties.length; i++) {530      expect(properties[i].value).to.be.equal('Serotonin increase');531      expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');532    }533  }534535  itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) =>  {536    const collection = await helper.nft.mintCollection(alice);537    const token = await collection.mintToken(alice);538    await testAssignPropertiesAccordingToPermissions(token, 1n);539  });540541  itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {542    const collection = await helper.rft.mintCollection(alice);543    const token = await collection.mintToken(alice, 100n);544    await testAssignPropertiesAccordingToPermissions(token, 100n);545  });546547  async function testChangesPropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {548    await token.collection.addAdmin(alice, {Substrate: bob.address});549    await token.transfer(alice, {Substrate: charlie.address}, pieces);550551    const propertyKeys: string[] = [];552    let i = 0;553    for (const permission of permissions) {554      i++;555      if (!permission.permission.mutable) continue;556      557      let j = 0;558      for (const signer of permission.signers) {559        j++;560        const key = i + '_' + signer.address;561        propertyKeys.push(key);562563        await expect(564          token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 565          `on setting permission #${i} by alice`,566        ).to.be.fulfilled;567568        await expect(569          token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 570          `on adding property #${i} by signer #${j}`,571        ).to.be.fulfilled;572573        await expect(574          token.setProperties(signer, [{key, value: 'Serotonin stable'}]), 575          `on changing property #${i} by signer #${j}`,576        ).to.be.fulfilled;577      }578    }579580    const properties = await token.getProperties(propertyKeys);581    const tokenData = await token.getData();582    for (let i = 0; i < properties.length; i++) {583      expect(properties[i].value).to.be.equal('Serotonin stable');584      expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');585    }586  }587588  itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) =>  {589    const collection = await helper.nft.mintCollection(alice);590    const token = await collection.mintToken(alice);591    await testChangesPropertiesAccordingPermission(token, 1n);592  });593594  itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {595    const collection = await helper.rft.mintCollection(alice);596    const token = await collection.mintToken(alice, 100n);597    await testChangesPropertiesAccordingPermission(token, 100n);598  });599600  async function testDeletePropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {601    await token.collection.addAdmin(alice, {Substrate: bob.address});602    await token.transfer(alice, {Substrate: charlie.address}, pieces);603604    const propertyKeys: string[] = [];605    let i = 0;606607    for (const permission of permissions) {608      i++;609      if (!permission.permission.mutable) continue;610      611      let j = 0;612      for (const signer of permission.signers) {613        j++;614        const key = i + '_' + signer.address;615        propertyKeys.push(key);616617        await expect(618          token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 619          `on setting permission #${i} by alice`,620        ).to.be.fulfilled;621622        await expect(623          token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 624          `on adding property #${i} by signer #${j}`,625        ).to.be.fulfilled;626627        await expect(628          token.deleteProperties(signer, [key]), 629          `on deleting property #${i} by signer #${j}`,630        ).to.be.fulfilled;631      }632    }633634    expect(await token.getProperties(propertyKeys)).to.be.empty;635    expect((await token.getData())!.properties).to.be.empty;636  }637  638  itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) =>  {639    const collection = await helper.nft.mintCollection(alice);640    const token = await collection.mintToken(alice);641    await testDeletePropertiesAccordingPermission(token, 1n);642  });643644  itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {645    const collection = await helper.rft.mintCollection(alice);646    const token = await collection.mintToken(alice, 100n);647    await testDeletePropertiesAccordingPermission(token, 100n);648  });649650  itSub('Assigns properties to a nested token according to permissions', async ({helper}) =>  {651    const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});652    const collectionB = await helper.nft.mintCollection(alice);653    const targetToken = await collectionA.mintToken(alice);654    const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAddress());655656    await collectionB.addAdmin(alice, {Substrate: bob.address});657    await targetToken.transfer(alice, {Substrate: charlie.address});658659    const propertyKeys: string[] = [];660    let i = 0;661    for (const permission of permissions) {662      i++;663      let j = 0;664      for (const signer of permission.signers) {665        j++;666        const key = i + '_' + signer.address;667        propertyKeys.push(key);668        669        await expect(670          nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 671          `on setting permission #${i} by alice`,672        ).to.be.fulfilled;673674        await expect(675          nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 676          `on adding property #${i} by signer #${j}`,677        ).to.be.fulfilled;678      }679680    }681682    const properties = await nestedToken.getProperties(propertyKeys);683    const tokenData = await nestedToken.getData();684    for (let i = 0; i < properties.length; i++) {685      expect(properties[i].value).to.be.equal('Serotonin increase');686      expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');687    }688    expect(await targetToken.getProperties()).to.be.empty;689  });690691  itSub('Changes properties of a nested token according to permissions', async ({helper}) =>  {692    const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});693    const collectionB = await helper.nft.mintCollection(alice);694    const targetToken = await collectionA.mintToken(alice);695    const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAddress());696697    await collectionB.addAdmin(alice, {Substrate: bob.address});698    await targetToken.transfer(alice, {Substrate: charlie.address});699700    const propertyKeys: string[] = [];701    let i = 0;702    for (const permission of permissions) {703      i++;704      if (!permission.permission.mutable) continue;705      706      let j = 0;707      for (const signer of permission.signers) {708        j++;709        const key = i + '_' + signer.address;710        propertyKeys.push(key);711712        await expect(713          nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 714          `on setting permission #${i} by alice`,715        ).to.be.fulfilled;716717        await expect(718          nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 719          `on adding property #${i} by signer #${j}`,720        ).to.be.fulfilled;721722        await expect(723          nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 724          `on changing property #${i} by signer #${j}`,725        ).to.be.fulfilled;726      }727    }728729    const properties = await nestedToken.getProperties(propertyKeys);730    const tokenData = await nestedToken.getData();731    for (let i = 0; i < properties.length; i++) {732      expect(properties[i].value).to.be.equal('Serotonin stable');733      expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');734    }735    expect(await targetToken.getProperties()).to.be.empty;736  });737738  itSub('Deletes properties of a nested token according to permissions', async ({helper}) =>  {739    const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});740    const collectionB = await helper.nft.mintCollection(alice);741    const targetToken = await collectionA.mintToken(alice);742    const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAddress());743744    await collectionB.addAdmin(alice, {Substrate: bob.address});745    await targetToken.transfer(alice, {Substrate: charlie.address});746747    const propertyKeys: string[] = [];748    let i = 0;749    for (const permission of permissions) {750      i++;751      if (!permission.permission.mutable) continue;752      753      let j = 0;754      for (const signer of permission.signers) {755        j++;756        const key = i + '_' + signer.address;757        propertyKeys.push(key);758759        await expect(760          nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 761          `on setting permission #${i} by alice`,762        ).to.be.fulfilled;763764        await expect(765          nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 766          `on adding property #${i} by signer #${j}`,767        ).to.be.fulfilled;768769        await expect(770          nestedToken.deleteProperties(signer, [key]), 771          `on deleting property #${i} by signer #${j}`,772        ).to.be.fulfilled;773      }774    }775776    expect(await nestedToken.getProperties(propertyKeys)).to.be.empty;777    expect((await nestedToken.getData())!.properties).to.be.empty;778    expect(await targetToken.getProperties()).to.be.empty;779  });780});781782describe('Negative Integration Test: Token Properties', () => {783  let alice: IKeyringPair; // collection owner784  let bob: IKeyringPair; // collection admin785  let charlie: IKeyringPair; // token owner786787  let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];788789  before(async () => {790    await usingPlaygrounds(async (helper, privateKey) => {791      const donor = privateKey('//Alice');792      let dave: IKeyringPair;793      [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);794795      // todo:playgrounds probably separate these tests later796      constitution = [797        {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},798        {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},799        {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},800        {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},801        {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},802        {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},803      ];804    });805  });806807  async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise<number> {808    return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace;809  }810811  async function prepare(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint): Promise<number> {812    await token.collection.addAdmin(alice, {Substrate: bob.address});813    await token.transfer(alice, {Substrate: charlie.address}, pieces);814815    let i = 0;816    for (const passage of constitution) {817      i++;818      const signer = passage.signers[0];819      820      await expect(821        token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), 822        `on setting permission ${i} by alice`,823      ).to.be.fulfilled;824825      await expect(826        token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), 827        `on adding property ${i} by ${signer.address}`,828      ).to.be.fulfilled;829    }830831    const originalSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 832    return originalSpace;833  }834835  async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {836    const originalSpace = await prepare(token, pieces);837838    let i = 0;839    for (const forbiddance of constitution) {840      i++;841      if (!forbiddance.permission.mutable) continue;842843      await expect(844        token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 845        `on failing to change property ${i} by the malefactor`,846      ).to.be.rejectedWith(/common\.NoPermission/);847848      await expect(849        token.deleteProperties(forbiddance.sinner, [`${i}`]), 850        `on failing to delete property ${i} by the malefactor`,851      ).to.be.rejectedWith(/common\.NoPermission/);852    }853854    const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 855    expect(consumedSpace).to.be.equal(originalSpace);856  }857858  itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) =>  {859    const collection = await helper.nft.mintCollection(alice);860    const token = await collection.mintToken(alice);861    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);862  });863864  itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {865    const collection = await helper.rft.mintCollection(alice);866    const token = await collection.mintToken(alice, 100n);867    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);868  });869870  async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {871    const originalSpace = await prepare(token, pieces);872873    let i = 0;874    for (const permission of constitution) {875      i++;876      if (permission.permission.mutable) continue;877878      await expect(879        token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 880        `on failing to change property ${i} by signer #0`,881      ).to.be.rejectedWith(/common\.NoPermission/);882883      await expect(884        token.deleteProperties(permission.signers[0], [i.toString()]), 885        `on failing to delete property ${i} by signer #0`,886      ).to.be.rejectedWith(/common\.NoPermission/);887    }888  889    const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 890    expect(consumedSpace).to.be.equal(originalSpace);891  }892893  itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) =>  {894    const collection = await helper.nft.mintCollection(alice);895    const token = await collection.mintToken(alice);896    await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);897  });898899  itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {900    const collection = await helper.rft.mintCollection(alice);901    const token = await collection.mintToken(alice, 100n);902    await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);903  });904905  async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {906    const originalSpace = await prepare(token, pieces);907908    await expect(909      token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 910      'on failing to add a previously non-existent property',911    ).to.be.rejectedWith(/common\.NoPermission/);912      913    await expect(914      token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 915      'on setting a new non-permitted property',916    ).to.be.fulfilled;917918    await expect(919      token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 920      'on failing to add a property forbidden by the \'None\' permission',921    ).to.be.rejectedWith(/common\.NoPermission/);922923    expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;924      925    const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 926    expect(consumedSpace).to.be.equal(originalSpace);927  }928929  itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) =>  {930    const collection = await helper.nft.mintCollection(alice);931    const token = await collection.mintToken(alice);932    await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);933  });934935  itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {936    const collection = await helper.rft.mintCollection(alice);937    const token = await collection.mintToken(alice, 100n);938    await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);939  });940941  async function testForbidsAddingTooManyProperties(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) {942    const originalSpace = await prepare(token, pieces);943944    await expect(945      token.collection.setTokenPropertyPermissions(alice, [946        {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 947        {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},948      ]), 949      'on setting new permissions for properties',950    ).to.be.fulfilled;951952    // Mute the general tx parsing error953    {954      console.error = () => {};955      await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))956        .to.be.rejected;957    }958959    await expect(token.setProperties(alice, [960      {key: 'a_holy_book', value: 'word '.repeat(3277)}, 961      {key: 'young_years', value: 'neverending'.repeat(1490)},962    ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);963  964    expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty;965    const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 966    expect(consumedSpace).to.be.equal(originalSpace);967  }968969  itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) =>  {970    const collection = await helper.nft.mintCollection(alice);971    const token = await collection.mintToken(alice);972    await testForbidsAddingTooManyProperties(token, 1n);973  });974975  itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {976    const collection = await helper.rft.mintCollection(alice);977    const token = await collection.mintToken(alice, 100n);978    await testForbidsAddingTooManyProperties(token, 100n);979  });980});981982describe('ReFungible token properties permissions tests', () => {983  let alice: IKeyringPair;984  let bob: IKeyringPair;985  let charlie: IKeyringPair;986987  before(async function() {988    await usingPlaygrounds(async (helper, privateKey) => {989      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);990991      const donor = privateKey('//Alice');992      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);993    });994  });995996  async function prepare(helper: UniqueHelper): Promise<UniqueRFTToken> {997    const collection = await helper.rft.mintCollection(alice);998    const token = await collection.mintToken(alice, 100n);999    1000    await collection.addAdmin(alice, {Substrate: bob.address});1001    await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);1002    1003    return token;1004  }10051006  itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) =>  {1007    const token = await prepare(helper);10081009    await token.transfer(alice, {Substrate: charlie.address}, 33n);10101011    await expect(token.setProperties(alice, [1012      {key: 'fractals', value: 'multiverse'}, 1013    ])).to.be.rejectedWith(/common\.NoPermission/);1014  });10151016  itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) =>  {1017    const token = await prepare(helper);10181019    await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1020      .to.be.fulfilled;10211022    await expect(token.setProperties(alice, [1023      {key: 'fractals', value: 'multiverse'}, 1024    ])).to.be.fulfilled;10251026    await token.transfer(alice, {Substrate: charlie.address}, 33n);10271028    await expect(token.setProperties(alice, [1029      {key: 'fractals', value: 'want to rule the world'}, 1030    ])).to.be.rejectedWith(/common\.NoPermission/);1031  });10321033  itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) =>  {1034    const token = await prepare(helper);10351036    await expect(token.setProperties(alice, [1037      {key: 'fractals', value: 'one headline - why believe it'}, 1038    ])).to.be.fulfilled;10391040    await token.transfer(alice, {Substrate: charlie.address}, 33n);10411042    await expect(token.deleteProperties(alice, ['fractals'])).1043      to.be.rejectedWith(/common\.NoPermission/);1044  });10451046  itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) =>  {1047    const token = await prepare(helper);10481049    await token.transfer(alice, {Substrate: charlie.address}, 33n);10501051    await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1052      .to.be.fulfilled;10531054    await expect(token.setProperties(alice, [1055      {key: 'fractals', value: 'multiverse'}, 1056    ])).to.be.fulfilled;1057  });1058});