git.delta.rocks / unique-network / refs/commits / f27580fc65c7

difftreelog

source

tests/src/nesting/properties.test.ts46.1 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/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds';19import {UniqueHelper, UniqueBaseCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../util/playgrounds/unique';202122describe('Composite Properties Test', () => {23  let alice: IKeyringPair;2425  before(async () => {26    await usingPlaygrounds(async (helper, privateKey) => {27      const donor = privateKey('//Alice');28      [alice] = await helper.arrange.createAccounts([50n], donor);29    });30  });3132  async function testMakeSureSuppliesRequired(baseCollection: UniqueNFTCollection | UniqueRFTCollection) {3334    const collectionOption = await baseCollection.getOptions();35    expect(collectionOption).is.not.null;36    let collection = collectionOption;37    expect(collection.tokenPropertyPermissions).to.be.empty;38    expect(collection.properties).to.be.empty;3940    const propertyPermissions = [41      {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}},42      {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}},43    ];44    await expect(await baseCollection.setTokenPropertyPermissions(alice, propertyPermissions)).to.be.true;4546    const collectionProperties = [47      {key: 'black_hole', value: 'LIGO'},48      {key: 'electron', value: 'come bond'}, 49    ];50    51    await expect(await baseCollection.setProperties(alice, collectionProperties)).to.be.true;5253    collection = await baseCollection.getOptions();54    expect(collection.tokenPropertyPermissions).to.be.deep.equal(propertyPermissions);55    expect(collection.properties).to.be.deep.equal(collectionProperties);56  }5758  itSub('Makes sure collectionById supplies required fields for NFT',  async ({helper}) => {59    await testMakeSureSuppliesRequired(await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}));60  });6162  itSub.ifWithPallets('Makes sure collectionById supplies required fields for ReFungible', [Pallets.ReFungible],  async ({helper}) => {63    await testMakeSureSuppliesRequired(await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}));64  });65});66// ---------- COLLECTION PROPERTIES6768describe('Integration Test: Collection Properties', () => {69  let alice: IKeyringPair;70  let bob: IKeyringPair;7172  before(async () => {73    await usingPlaygrounds(async (helper, privateKey) => {74      const donor = privateKey('//Alice');75      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);76    });77  });7879  itSub('Properties are initially empty', async ({helper}) => {80    const collection = await helper.nft.mintCollection(alice);81    const properties = await collection.getProperties();82    expect(properties).to.be.empty;83  });8485  async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {86    // As owner87    await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;8889    await collection.addAdmin(alice, {Substrate: bob.address});9091    // As administrator92    await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;9394    const properties = await collection.getProperties();95    expect(properties).to.include.deep.members([96      {key: 'electron', value: 'come bond'},97      {key: 'black_hole', value: ''},98    ]);99  }100101  itSub('Sets properties for a NFT collection', async ({helper}) =>  {102    await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));103  });104105  itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {106    await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));107  });108109  async function testCheckValidNames(collection: UniqueBaseCollection) {110    // alpha symbols111    await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;112113    // numeric symbols114    await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled;115116    // underscore symbol117    await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled;118119    // dash symbol120    await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled;121122    // dot symbol123    await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled;124125    const properties = await collection.getProperties();126    expect(properties).to.include.deep.members([127      {key: 'answer', value: ''},128      {key: '451', value: ''},129      {key: 'black_hole', value: ''},130      {key: '-', value: ''},131      {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},132    ]);133  }134135  itSub('Check valid names for NFT collection properties keys', async ({helper}) =>  {136    await testCheckValidNames(await helper.nft.mintCollection(alice));137  });138139  itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {140    await testCheckValidNames(await helper.rft.mintCollection(alice));141  });142143  async function testChangesProperties(collection: UniqueBaseCollection) {144    await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;145146    // Mutate the properties147    await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;148149    const properties = await collection.getProperties();150    expect(properties).to.include.deep.members([151      {key: 'electron', value: 'come bond'},152      {key: 'black_hole', value: 'LIGO'},153    ]);154  }155156  itSub('Changes properties of a NFT collection', async ({helper}) =>  {157    await testChangesProperties(await helper.nft.mintCollection(alice));158  });159160  itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {161    await testChangesProperties(await helper.rft.mintCollection(alice));162  });163164  async function testDeleteProperties(collection: UniqueBaseCollection) {165    await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;166167    await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled;168169    const properties = await collection.getProperties(['black_hole', 'electron']);170    expect(properties).to.be.deep.equal([171      {key: 'black_hole', value: 'LIGO'},172    ]);173  }174175  itSub('Deletes properties of a NFT collection', async ({helper}) =>  {176    await testDeleteProperties(await helper.nft.mintCollection(alice));177  });178179  itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {180    await testDeleteProperties(await helper.rft.mintCollection(alice));181  });182});183184describe('Negative Integration Test: Collection Properties', () => {185  let alice: IKeyringPair;186  let bob: IKeyringPair;187188  before(async () => {189    await usingPlaygrounds(async (helper, privateKey) => {190      const donor = privateKey('//Alice');191      [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);192    });193  });194  195  async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueBaseCollection) {  196    await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))197      .to.be.rejectedWith(/common\.NoPermission/);198199    const properties = await collection.getProperties();200    expect(properties).to.be.empty;201  }202203  itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) =>  {204    await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));205  });206207  itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {208    await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));209  });210  211  async function testFailsSetPropertiesThatExeedLimits(collection: UniqueBaseCollection) {212    const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();213  214    // Mute the general tx parsing error, too many bytes to process215    {216      console.error = () => {};217      await expect(collection.setProperties(alice, [218        {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))},219      ])).to.be.rejected;220    }221222    expect(await collection.getProperties(['electron'])).to.be.empty;223224    await expect(collection.setProperties(alice, [225      {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, 226      {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, 227    ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);228229    expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;230  }231232  itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) =>  {233    await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));234  });235236  itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {237    await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));238  });239  240  async function testFailsSetMorePropertiesThanAllowed(collection: UniqueBaseCollection) {241    const propertiesToBeSet = [];242    for (let i = 0; i < 65; i++) {243      propertiesToBeSet.push({244        key: 'electron_' + i,245        value: Math.random() > 0.5 ? 'high' : 'low',246      });247    }248249    await expect(collection.setProperties(alice, propertiesToBeSet)).250      to.be.rejectedWith(/common\.PropertyLimitReached/);251252    const properties = await collection.getProperties();253    expect(properties).to.be.empty;254  }255256  itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) =>  {257    await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));258  });259260  itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {261    await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));262  });263  264  async function testFailsSetPropertiesWithInvalidNames(collection: UniqueBaseCollection) {265    const invalidProperties = [266      [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],267      [{key: 'Mr/Sandman', value: 'Bring me a gene'}],268      [{key: 'déjà vu', value: 'hmm...'}],269    ];270271    for (let i = 0; i < invalidProperties.length; i++) {272      await expect(273        collection.setProperties(alice, invalidProperties[i]), 274        `on rejecting the new badly-named property #${i}`,275      ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);276    }277278    await expect(279      collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 280      'on rejecting an unnamed property',281    ).to.be.rejectedWith(/common\.EmptyPropertyKey/);282283    await expect(284      collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 285      'on setting the correctly-but-still-badly-named property',286    ).to.be.fulfilled;287288    const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');289290    const properties = await collection.getProperties(keys);291    expect(properties).to.be.deep.equal([292      {key: 'CRISPR-Cas9', value: 'rewriting nature!'},293    ]);294295    for (let i = 0; i < invalidProperties.length; i++) {296      await expect(297        collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), 298        `on trying to delete the non-existent badly-named property #${i}`,299      ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);300    }301  }302303  itSub('Fails to set properties with invalid names (NFT)', async ({helper}) =>  {304    await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));305  });306307  itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {308    await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));309  });310});311312// ---------- ACCESS RIGHTS313314describe('Integration Test: Access Rights to Token Properties', () => {315  let alice: IKeyringPair;316  let bob: IKeyringPair;317318  before(async () => {319    await usingPlaygrounds(async (helper, privateKey) => {320      const donor = privateKey('//Alice');321      [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);322    });323  });324  325  itSub('Reads access rights to properties of a collection', async ({helper}) =>  {326    const collection = await helper.nft.mintCollection(alice);327    const propertyRights = (await helper.callRpc('api.query.common.collectionPropertyPermissions', [collection.collectionId])).toJSON();328    expect(propertyRights).to.be.empty;329  });330  331  async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) {  332    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}]))333      .to.be.fulfilled;334335    await collection.addAdmin(alice, {Substrate: bob.address});336337    await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]))338      .to.be.fulfilled;339340    const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']);341    expect(propertyRights).to.include.deep.members([342      {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}},343      {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},344    ]);345  }346347  itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) =>  {348    await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice));349  });350351  itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => {352    await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice));353  });354  355  async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) {356    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]))357      .to.be.fulfilled;358359    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))360      .to.be.fulfilled;361362    const propertyRights = await collection.getPropertyPermissions();363    expect(propertyRights).to.be.deep.equal([364      {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},365    ]);366  }367368  itSub('Changes access rights to properties of a NFT collection', async ({helper}) =>  {369    await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice));370  });371372  itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {373    await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice));374  });375});376377describe('Negative Integration Test: Access Rights to Token Properties', () => {378  let alice: IKeyringPair;379  let bob: IKeyringPair;380381  before(async () => {382    await usingPlaygrounds(async (helper, privateKey) => {383      const donor = privateKey('//Alice');384      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);385    });386  });387388  async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) {389    await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]))390      .to.be.rejectedWith(/common\.NoPermission/);391392    const propertyRights = await collection.getPropertyPermissions(['skullduggery']);393    expect(propertyRights).to.be.empty;394  }395396  itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) =>  {397    await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice));398  });399400  itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => {401    await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice));402  });403404  async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) {  405    const constitution = [];406    for (let i = 0; i < 65; i++) {407      constitution.push({408        key: 'property_' + i,409        permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},410      });411    }412413    await expect(collection.setTokenPropertyPermissions(alice, constitution))414      .to.be.rejectedWith(/common\.PropertyLimitReached/);415416    const propertyRights = await collection.getPropertyPermissions();417    expect(propertyRights).to.be.empty;418  }419420  itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) =>  {421    await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice));422  });423424  itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => {425    await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice));426  });427428  async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) {429    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]))430      .to.be.fulfilled;431432    await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}]))433      .to.be.rejectedWith(/common\.NoPermission/);434435    const propertyRights = await collection.getPropertyPermissions(['skullduggery']);436    expect(propertyRights).to.deep.equal([437      {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},438    ]);439  }440441  itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) =>  {442    await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice));443  });444445  itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => {446    await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice));447  });448449  async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) {450    const invalidProperties = [451      [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],452      [{key: 'G#4', permission: {tokenOwner: true}}],453      [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],454    ];455456    for (let i = 0; i < invalidProperties.length; i++) {457      await expect(458        collection.setTokenPropertyPermissions(alice, invalidProperties[i]), 459        `on setting the new badly-named property #${i}`,460      ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);461    }462463    await expect(464      collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 465      'on rejecting an unnamed property',466    ).to.be.rejectedWith(/common\.EmptyPropertyKey/);467468    const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string469    await expect(470      collection.setTokenPropertyPermissions(alice, [471        {key: correctKey, permission: {collectionAdmin: true}},472      ]), 473      'on setting the correctly-but-still-badly-named property',474    ).to.be.fulfilled;475476    const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');477478    const propertyRights = await collection.getPropertyPermissions(keys);479    expect(propertyRights).to.be.deep.equal([480      {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},481    ]);482  }483484  itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) =>  {485    await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));486  });487488  itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {489    await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));490  });491});492493// ---------- TOKEN PROPERTIES494495describe('Integration Test: Token Properties', () => {496  let alice: IKeyringPair; // collection owner497  let bob: IKeyringPair; // collection admin498  let charlie: IKeyringPair; // token owner499500  let permissions: {permission: any, signers: IKeyringPair[]}[];501502  before(async () => {503    await usingPlaygrounds(async (helper, privateKey) => {504      const donor = privateKey('//Alice');505      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);506    });507508    // todo:playgrounds probably separate these tests later509    permissions = [510      {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},511      {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]},512      {permission: {mutable: true, tokenOwner: true}, signers: [charlie]},513      {permission: {mutable: false, tokenOwner: true}, signers: [charlie]},514      {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},515      {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]},516    ];517  });518  519  async function testReadsYetEmptyProperties(token: UniqueNFToken | UniqueRFToken) {520    const properties = await token.getProperties();521    expect(properties).to.be.empty;522523    const tokenData = await token.getData();524    expect(tokenData!.properties).to.be.empty;525  }526527  itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => {528    const collection = await helper.nft.mintCollection(alice);529    const token = await collection.mintToken(alice);530    await testReadsYetEmptyProperties(token);531  });532533  itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {534    const collection = await helper.rft.mintCollection(alice);535    const token = await collection.mintToken(alice);536    await testReadsYetEmptyProperties(token);537  });538539  async function testAssignPropertiesAccordingToPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {540    await token.collection.addAdmin(alice, {Substrate: bob.address});541    await token.transfer(alice, {Substrate: charlie.address}, pieces);542543    const propertyKeys: string[] = [];544    let i = 0;545    for (const permission of permissions) {546      i++;547      let j = 0;548      for (const signer of permission.signers) {549        j++;550        const key = i + '_' + signer.address;551        propertyKeys.push(key);552553        await expect(554          token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 555          `on setting permission #${i} by alice`,556        ).to.be.fulfilled;557558        await expect(559          token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), 560          `on adding property #${i} by signer #${j}`,561        ).to.be.fulfilled;562      }563    }564565    const properties = await token.getProperties(propertyKeys);566    const tokenData = await token.getData();567    for (let i = 0; i < properties.length; i++) {568      expect(properties[i].value).to.be.equal('Serotonin increase');569      expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');570    }571  }572573  itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) =>  {574    const collection = await helper.nft.mintCollection(alice);575    const token = await collection.mintToken(alice);576    await testAssignPropertiesAccordingToPermissions(token, 1n);577  });578579  itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {580    const collection = await helper.rft.mintCollection(alice);581    const token = await collection.mintToken(alice, 100n);582    await testAssignPropertiesAccordingToPermissions(token, 100n);583  });584585  async function testChangesPropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {586    await token.collection.addAdmin(alice, {Substrate: bob.address});587    await token.transfer(alice, {Substrate: charlie.address}, pieces);588589    const propertyKeys: string[] = [];590    let i = 0;591    for (const permission of permissions) {592      i++;593      if (!permission.permission.mutable) continue;594      595      let j = 0;596      for (const signer of permission.signers) {597        j++;598        const key = i + '_' + signer.address;599        propertyKeys.push(key);600601        await expect(602          token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 603          `on setting permission #${i} by alice`,604        ).to.be.fulfilled;605606        await expect(607          token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 608          `on adding property #${i} by signer #${j}`,609        ).to.be.fulfilled;610611        await expect(612          token.setProperties(signer, [{key, value: 'Serotonin stable'}]), 613          `on changing property #${i} by signer #${j}`,614        ).to.be.fulfilled;615      }616    }617618    const properties = await token.getProperties(propertyKeys);619    const tokenData = await token.getData();620    for (let i = 0; i < properties.length; i++) {621      expect(properties[i].value).to.be.equal('Serotonin stable');622      expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');623    }624  }625626  itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) =>  {627    const collection = await helper.nft.mintCollection(alice);628    const token = await collection.mintToken(alice);629    await testChangesPropertiesAccordingPermission(token, 1n);630  });631632  itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {633    const collection = await helper.rft.mintCollection(alice);634    const token = await collection.mintToken(alice, 100n);635    await testChangesPropertiesAccordingPermission(token, 100n);636  });637638  async function testDeletePropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {639    await token.collection.addAdmin(alice, {Substrate: bob.address});640    await token.transfer(alice, {Substrate: charlie.address}, pieces);641642    const propertyKeys: string[] = [];643    let i = 0;644645    for (const permission of permissions) {646      i++;647      if (!permission.permission.mutable) continue;648      649      let j = 0;650      for (const signer of permission.signers) {651        j++;652        const key = i + '_' + signer.address;653        propertyKeys.push(key);654655        await expect(656          token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 657          `on setting permission #${i} by alice`,658        ).to.be.fulfilled;659660        await expect(661          token.setProperties(signer, [{key, value: 'Serotonin increase'}]), 662          `on adding property #${i} by signer #${j}`,663        ).to.be.fulfilled;664665        await expect(666          token.deleteProperties(signer, [key]), 667          `on deleting property #${i} by signer #${j}`,668        ).to.be.fulfilled;669      }670    }671672    expect(await token.getProperties(propertyKeys)).to.be.empty;673    expect((await token.getData())!.properties).to.be.empty;674  }675  676  itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) =>  {677    const collection = await helper.nft.mintCollection(alice);678    const token = await collection.mintToken(alice);679    await testDeletePropertiesAccordingPermission(token, 1n);680  });681682  itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {683    const collection = await helper.rft.mintCollection(alice);684    const token = await collection.mintToken(alice, 100n);685    await testDeletePropertiesAccordingPermission(token, 100n);686  });687688  itSub('Assigns properties to a nested token according to permissions', async ({helper}) =>  {689    const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});690    const collectionB = await helper.nft.mintCollection(alice);691    const targetToken = await collectionA.mintToken(alice);692    const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());693694    await collectionB.addAdmin(alice, {Substrate: bob.address});695    await targetToken.transfer(alice, {Substrate: charlie.address});696697    const propertyKeys: string[] = [];698    let i = 0;699    for (const permission of permissions) {700      i++;701      let j = 0;702      for (const signer of permission.signers) {703        j++;704        const key = i + '_' + signer.address;705        propertyKeys.push(key);706        707        await expect(708          nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 709          `on setting permission #${i} by alice`,710        ).to.be.fulfilled;711712        await expect(713          nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 714          `on adding property #${i} by signer #${j}`,715        ).to.be.fulfilled;716      }717    }718719    const properties = await nestedToken.getProperties(propertyKeys);720    const tokenData = await nestedToken.getData();721    for (let i = 0; i < properties.length; i++) {722      expect(properties[i].value).to.be.equal('Serotonin increase');723      expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase');724    }725    expect(await targetToken.getProperties()).to.be.empty;726  });727728  itSub('Changes properties of a nested token according to permissions', async ({helper}) =>  {729    const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});730    const collectionB = await helper.nft.mintCollection(alice);731    const targetToken = await collectionA.mintToken(alice);732    const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());733734    await collectionB.addAdmin(alice, {Substrate: bob.address});735    await targetToken.transfer(alice, {Substrate: charlie.address});736737    const propertyKeys: string[] = [];738    let i = 0;739    for (const permission of permissions) {740      i++;741      if (!permission.permission.mutable) continue;742      743      let j = 0;744      for (const signer of permission.signers) {745        j++;746        const key = i + '_' + signer.address;747        propertyKeys.push(key);748749        await expect(750          nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 751          `on setting permission #${i} by alice`,752        ).to.be.fulfilled;753754        await expect(755          nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 756          `on adding property #${i} by signer #${j}`,757        ).to.be.fulfilled;758759        await expect(760          nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), 761          `on changing property #${i} by signer #${j}`,762        ).to.be.fulfilled;763      }764    }765766    const properties = await nestedToken.getProperties(propertyKeys);767    const tokenData = await nestedToken.getData();768    for (let i = 0; i < properties.length; i++) {769      expect(properties[i].value).to.be.equal('Serotonin stable');770      expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable');771    }772    expect(await targetToken.getProperties()).to.be.empty;773  });774775  itSub('Deletes properties of a nested token according to permissions', async ({helper}) =>  {776    const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});777    const collectionB = await helper.nft.mintCollection(alice);778    const targetToken = await collectionA.mintToken(alice);779    const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount());780781    await collectionB.addAdmin(alice, {Substrate: bob.address});782    await targetToken.transfer(alice, {Substrate: charlie.address});783784    const propertyKeys: string[] = [];785    let i = 0;786    for (const permission of permissions) {787      i++;788      if (!permission.permission.mutable) continue;789      790      let j = 0;791      for (const signer of permission.signers) {792        j++;793        const key = i + '_' + signer.address;794        propertyKeys.push(key);795796        await expect(797          nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), 798          `on setting permission #${i} by alice`,799        ).to.be.fulfilled;800801        await expect(802          nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), 803          `on adding property #${i} by signer #${j}`,804        ).to.be.fulfilled;805806        await expect(807          nestedToken.deleteProperties(signer, [key]), 808          `on deleting property #${i} by signer #${j}`,809        ).to.be.fulfilled;810      }811    }812813    expect(await nestedToken.getProperties(propertyKeys)).to.be.empty;814    expect((await nestedToken.getData())!.properties).to.be.empty;815    expect(await targetToken.getProperties()).to.be.empty;816  });817});818819describe('Negative Integration Test: Token Properties', () => {820  let alice: IKeyringPair; // collection owner821  let bob: IKeyringPair; // collection admin822  let charlie: IKeyringPair; // token owner823824  let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[];825826  before(async () => {827    await usingPlaygrounds(async (helper, privateKey) => {828      const donor = privateKey('//Alice');829      let dave: IKeyringPair;830      [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);831832      // todo:playgrounds probably separate these tests later833      constitution = [834        {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},835        {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie},836        {permission: {mutable: true, tokenOwner: true}, signers: [charlie], sinner: alice},837        {permission: {mutable: false, tokenOwner: true}, signers: [charlie], sinner: alice},838        {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},839        {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie], sinner: dave},840      ];841    });842  });843844  async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise<number> {845    return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace;846  }847848  async function prepare(token: UniqueNFToken | UniqueRFToken, pieces: bigint): Promise<number> {849    await token.collection.addAdmin(alice, {Substrate: bob.address});850    await token.transfer(alice, {Substrate: charlie.address}, pieces);851852    let i = 0;853    for (const passage of constitution) {854      i++;855      const signer = passage.signers[0];856      857      await expect(858        token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), 859        `on setting permission ${i} by alice`,860      ).to.be.fulfilled;861862      await expect(863        token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), 864        `on adding property ${i} by ${signer.address}`,865      ).to.be.fulfilled;866    }867868    const originalSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 869    return originalSpace;870  }871872  async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {873    const originalSpace = await prepare(token, pieces);874875    let i = 0;876    for (const forbiddance of constitution) {877      i++;878      if (!forbiddance.permission.mutable) continue;879880      await expect(881        token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), 882        `on failing to change property ${i} by the malefactor`,883      ).to.be.rejectedWith(/common\.NoPermission/);884885      await expect(886        token.deleteProperties(forbiddance.sinner, [`${i}`]), 887        `on failing to delete property ${i} by the malefactor`,888      ).to.be.rejectedWith(/common\.NoPermission/);889    }890891    const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 892    expect(consumedSpace).to.be.equal(originalSpace);893  }894895  itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) =>  {896    const collection = await helper.nft.mintCollection(alice);897    const token = await collection.mintToken(alice);898    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n);899  });900901  itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => {902    const collection = await helper.rft.mintCollection(alice);903    const token = await collection.mintToken(alice, 100n);904    await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n);905  });906907  async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {908    const originalSpace = await prepare(token, pieces);909910    let i = 0;911    for (const permission of constitution) {912      i++;913      if (permission.permission.mutable) continue;914915      await expect(916        token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), 917        `on failing to change property ${i} by signer #0`,918      ).to.be.rejectedWith(/common\.NoPermission/);919920      await expect(921        token.deleteProperties(permission.signers[0], [i.toString()]), 922        `on failing to delete property ${i} by signer #0`,923      ).to.be.rejectedWith(/common\.NoPermission/);924    }925  926    const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 927    expect(consumedSpace).to.be.equal(originalSpace);928  }929930  itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) =>  {931    const collection = await helper.nft.mintCollection(alice);932    const token = await collection.mintToken(alice);933    await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n);934  });935936  itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => {937    const collection = await helper.rft.mintCollection(alice);938    const token = await collection.mintToken(alice, 100n);939    await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n);940  });941942  async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {943    const originalSpace = await prepare(token, pieces);944945    await expect(946      token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 947      'on failing to add a previously non-existent property',948    ).to.be.rejectedWith(/common\.NoPermission/);949      950    await expect(951      token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 952      'on setting a new non-permitted property',953    ).to.be.fulfilled;954955    await expect(956      token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 957      'on failing to add a property forbidden by the \'None\' permission',958    ).to.be.rejectedWith(/common\.NoPermission/);959960    expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty;961      962    const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 963    expect(consumedSpace).to.be.equal(originalSpace);964  }965966  itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) =>  {967    const collection = await helper.nft.mintCollection(alice);968    const token = await collection.mintToken(alice);969    await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n);970  });971972  itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => {973    const collection = await helper.rft.mintCollection(alice);974    const token = await collection.mintToken(alice, 100n);975    await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n);976  });977978  async function testForbidsAddingTooManyProperties(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {979    const originalSpace = await prepare(token, pieces);980981    await expect(982      token.collection.setTokenPropertyPermissions(alice, [983        {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, 984        {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},985      ]), 986      'on setting new permissions for properties',987    ).to.be.fulfilled;988989    // Mute the general tx parsing error990    {991      console.error = () => {};992      await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]))993        .to.be.rejected;994    }995996    await expect(token.setProperties(alice, [997      {key: 'a_holy_book', value: 'word '.repeat(3277)}, 998      {key: 'young_years', value: 'neverending'.repeat(1490)},999    ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);1000  1001    expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty;1002    const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); 1003    expect(consumedSpace).to.be.equal(originalSpace);1004  }10051006  itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) =>  {1007    const collection = await helper.nft.mintCollection(alice);1008    const token = await collection.mintToken(alice);1009    await testForbidsAddingTooManyProperties(token, 1n);1010  });10111012  itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {1013    const collection = await helper.rft.mintCollection(alice);1014    const token = await collection.mintToken(alice, 100n);1015    await testForbidsAddingTooManyProperties(token, 100n);1016  });1017});10181019describe('ReFungible token properties permissions tests', () => {1020  let alice: IKeyringPair;1021  let bob: IKeyringPair;1022  let charlie: IKeyringPair;10231024  before(async function() {1025    await usingPlaygrounds(async (helper, privateKey) => {1026      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);10271028      const donor = privateKey('//Alice');1029      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);1030    });1031  });10321033  async function prepare(helper: UniqueHelper): Promise<UniqueRFToken> {1034    const collection = await helper.rft.mintCollection(alice);1035    const token = await collection.mintToken(alice, 100n);1036    1037    await collection.addAdmin(alice, {Substrate: bob.address});1038    await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]);1039    1040    return token;1041  }10421043  itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) =>  {1044    const token = await prepare(helper);10451046    await token.transfer(alice, {Substrate: charlie.address}, 33n);10471048    await expect(token.setProperties(alice, [1049      {key: 'fractals', value: 'multiverse'}, 1050    ])).to.be.rejectedWith(/common\.NoPermission/);1051  });10521053  itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) =>  {1054    const token = await prepare(helper);10551056    await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))1057      .to.be.fulfilled;10581059    await expect(token.setProperties(alice, [1060      {key: 'fractals', value: 'multiverse'}, 1061    ])).to.be.fulfilled;10621063    await token.transfer(alice, {Substrate: charlie.address}, 33n);10641065    await expect(token.setProperties(alice, [1066      {key: 'fractals', value: 'want to rule the world'}, 1067    ])).to.be.rejectedWith(/common\.NoPermission/);1068  });10691070  itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) =>  {1071    const token = await prepare(helper);10721073    await expect(token.setProperties(alice, [1074      {key: 'fractals', value: 'one headline - why believe it'}, 1075    ])).to.be.fulfilled;10761077    await token.transfer(alice, {Substrate: charlie.address}, 33n);10781079    await expect(token.deleteProperties(alice, ['fractals'])).1080      to.be.rejectedWith(/common\.NoPermission/);1081  });10821083  itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) =>  {1084    const token = await prepare(helper);10851086    await token.transfer(alice, {Substrate: charlie.address}, 33n);10871088    await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}]))1089      .to.be.fulfilled;10901091    await expect(token.setProperties(alice, [1092      {key: 'fractals', value: 'multiverse'}, 1093    ])).to.be.fulfilled;1094  });1095});