git.delta.rocks / unique-network / refs/commits / 2fcc0ad3d6a7

difftreelog

test forbid creating ApiPromise without set endpoint

Yaroslav Bolyukin2023-09-11parent: #348c8c0.patch.diff
in: master

4 files changed

modifiedtests/src/.outdated/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/.outdated/substrate/substrate-api.ts
+++ b/tests/src/.outdated/substrate/substrate-api.ts
@@ -71,6 +71,7 @@
 
 export async function getApiConnection(settings: ApiOptions | undefined = undefined): Promise<ApiPromise> {
   settings = settings || defaultApiOptions();
+  if(!settings.provider) throw new Error('provider was not set');
   const api = new ApiPromise(settings);
 
   if (api) {
modifiedtests/src/eth/createCollection.test.tsdiffbeforeafterboth
before · tests/src/eth/createCollection.test.ts
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.8//9// 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 {evmToAddress} from '@polkadot/util-crypto';19import {Pallets, requirePalletsOrSkip} from '../util';20import {expect, itEth, usingEthPlaygrounds} from './util';21import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types';22import {CollectionFlag, IEthCrossAccountId, TCollectionMode} from '../util/playgrounds/types';2324const DECIMALS = 18;25const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [26  [],  // properties27  [],  // tokenPropertyPermissions28  [],  // adminList29  [false, false, []],  // nestingSettings30  [],  // limits31  emptyAddress,  // pendingSponsor32  [0],  // flags33];3435type ElementOf<A> = A extends readonly (infer T)[] ? T : never;36function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {37  if(args.length === 0) {38    yield internalRest as any;39    return;40  }41  for(const value of args[0]) {42    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;43  }44}4546describe('Create collection from EVM', () => {47  let donor: IKeyringPair;48  let nominal: bigint;4950  before(async function() {51    await usingEthPlaygrounds(async (helper, privateKey) => {52      donor = await privateKey({url: import.meta.url});53      nominal = helper.balance.getOneTokenNominal();54    });55  });5657  describe('Fungible collection', () => {58    before(async function() {59      await usingEthPlaygrounds((helper) => {60        requirePalletsOrSkip(this, helper, [Pallets.Fungible]);61        return Promise.resolve();62      });63    });6465    itEth('Collection address exist', async ({helper}) => {66      const owner = await helper.eth.createAccountWithBalance(donor);67      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';68      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);6970      expect(await collectionHelpers71        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())72        .to.be.false;7374      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();75      expect(await collectionHelpers76        .methods.isCollectionExist(collectionAddress).call())77        .to.be.true;7879      // check collectionOwner:80      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);81      const collectionOwner = await collectionEvm.methods.collectionOwner().call();82      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));83    });8485    itEth('destroyCollection', async ({helper}) => {86      const owner = await helper.eth.createAccountWithBalance(donor);87      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();88      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);8990      const result = await collectionHelper.methods91        .destroyCollection(collectionAddress)92        .send({from: owner});9394      const events = helper.eth.normalizeEvents(result.events);9596      expect(events).to.be.deep.equal([97        {98          address: collectionHelper.options.address,99          event: 'CollectionDestroyed',100          args: {101            collectionId: collectionAddress,102          },103        },104      ]);105106      expect(await collectionHelper.methods107        .isCollectionExist(collectionAddress)108        .call()).to.be.false;109      expect(await helper.collection.getData(collectionId)).to.be.null;110    });111112    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {113      const owner = await helper.eth.createAccountWithBalance(donor);114      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);115      {116        const MAX_NAME_LENGTH = 64;117        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);118        const description = 'A';119        const tokenPrefix = 'A';120121        await expect(collectionHelper.methods122          .createCollection([123            collectionName,124            description,125            tokenPrefix,126            CollectionMode.Fungible,127            DECIMALS,128            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,129          ])130          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);131      }132      {133        const MAX_DESCRIPTION_LENGTH = 256;134        const collectionName = 'A';135        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);136        const tokenPrefix = 'A';137        await expect(collectionHelper.methods138          .createCollection([139            collectionName,140            description,141            tokenPrefix,142            CollectionMode.Fungible,143            DECIMALS,144            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,145          ])146          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);147      }148      {149        const MAX_TOKEN_PREFIX_LENGTH = 16;150        const collectionName = 'A';151        const description = 'A';152        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);153        await expect(collectionHelper.methods154          .createCollection([155            collectionName,156            description,157            tokenPrefix,158            CollectionMode.Fungible,159            DECIMALS,160            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,161          ])162          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);163      }164    });165166    itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => {167      const owner = await helper.eth.createAccountWithBalance(donor);168      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);169      const expects = [0n, 1n, 30n].map(async value => {170        await expect(collectionHelper.methods171          .createCollection([172            'Peasantry',173            'absolutely anything',174            'TWIW',175            CollectionMode.Fungible,176            DECIMALS,177            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,178          ])179          .call({value: Number(value * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');180      });181      await Promise.all(expects);182    });183  });184185  describe('Nonfungible collection', () => {186    before(async function() {187      await usingEthPlaygrounds((helper) => {188        requirePalletsOrSkip(this, helper, [Pallets.NFT]);189        return Promise.resolve();190      });191    });192193    itEth('Create collection', async ({helper}) => {194      const owner = await helper.eth.createAccountWithBalance(donor);195196      const name = 'CollectionEVM';197      const description = 'Some description';198      const prefix = 'token prefix';199200      // todo:playgrounds this might fail when in async environment.201      const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;202      const {collectionId, collectionAddress, events} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'nft')).send();203204      expect(events).to.be.deep.equal([205        {206          address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',207          event: 'CollectionCreated',208          args: {209            owner: owner,210            collectionId: collectionAddress,211          },212        },213      ]);214215      const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;216217      const collection = helper.nft.getCollectionObject(collectionId);218      const data = (await collection.getData())!;219220      expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);221      expect(collectionId).to.be.eq(collectionCountAfter);222      expect(data.name).to.be.eq(name);223      expect(data.description).to.be.eq(description);224      expect(data.raw.tokenPrefix).to.be.eq(prefix);225      expect(data.raw.mode).to.be.eq('NFT');226227      const options = await collection.getOptions();228229      expect(options.tokenPropertyPermissions).to.be.empty;230    });231232    // this test will occasionally fail when in async environment.233    itEth('Check collection address exist', async ({helper}) => {234      const owner = await helper.eth.createAccountWithBalance(donor);235236      const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;237      const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);238      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);239240      expect(await collectionHelpers.methods241        .isCollectionExist(expectedCollectionAddress)242        .call()).to.be.false;243244      await collectionHelpers.methods245        .createCollection([246          'A',247          'A',248          'A',249          CollectionMode.Nonfungible,250          0,251          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,252        ])253        .send({value: Number(2n * helper.balance.getOneTokenNominal())});254255      expect(await collectionHelpers.methods256        .isCollectionExist(expectedCollectionAddress)257        .call()).to.be.true;258    });259  });260261  describe('Create RFT collection from EVM', () => {262    before(async function() {263      await usingEthPlaygrounds((helper) => {264        requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);265        return Promise.resolve();266      });267    });268269    itEth('Create collection', async ({helper}) => {270      const owner = await helper.eth.createAccountWithBalance(donor);271272      const name = 'CollectionEVM';273      const description = 'Some description';274      const prefix = 'token prefix';275276      const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();277      const data = (await helper.rft.getData(collectionId))!;278      const collection = helper.rft.getCollectionObject(collectionId);279280      expect(data.name).to.be.eq(name);281      expect(data.description).to.be.eq(description);282      expect(data.raw.tokenPrefix).to.be.eq(prefix);283      expect(data.raw.mode).to.be.eq('ReFungible');284285      const options = await collection.getOptions();286287      expect(options.tokenPropertyPermissions).to.be.empty;288    });289290    itEth('Create collection with properties & get description', async ({helper}) => {291      const owner = await helper.eth.createAccountWithBalance(donor);292293      const name = 'CollectionEVM';294      const description = 'Some description';295      const prefix = 'token prefix';296      const baseUri = 'BaseURI';297298      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);299      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');300301      const collection = helper.rft.getCollectionObject(collectionId);302      const data = (await collection.getData())!;303304      expect(data.name).to.be.eq(name);305      expect(data.description).to.be.eq(description);306      expect(data.raw.tokenPrefix).to.be.eq(prefix);307      expect(data.raw.mode).to.be.eq('ReFungible');308309      expect(await contract.methods.description().call()).to.deep.equal(description);310311      const options = await collection.getOptions();312      expect(options.tokenPropertyPermissions).to.be.deep.equal([313        {314          key: 'URI',315          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},316        },317        {318          key: 'URISuffix',319          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},320        },321      ]);322    });323324    itEth('Set sponsorship', async ({helper}) => {325      const owner = await helper.eth.createAccountWithBalance(donor);326      const sponsor = await helper.eth.createAccountWithBalance(donor);327      const ss58Format = helper.chain.getChainProperties().ss58Format;328      const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();329330      const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);331      const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);332      await collection.methods.setCollectionSponsorCross(sponsorCross).send();333334      let data = (await helper.rft.getData(collectionId))!;335      expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));336337      await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');338339      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);340      await sponsorCollection.methods.confirmCollectionSponsorship().send();341342      data = (await helper.rft.getData(collectionId))!;343      expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));344    });345346    itEth('Collection address exist', async ({helper}) => {347      const owner = await helper.eth.createAccountWithBalance(donor);348      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';349      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);350351      expect(await collectionHelpers352        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())353        .to.be.false;354355      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();356      expect(await collectionHelpers357        .methods.isCollectionExist(collectionAddress).call())358        .to.be.true;359360      // check collectionOwner:361      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);362      const collectionOwner = await collectionEvm.methods.collectionOwner().call();363      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));364    });365366    itEth('destroyCollection', async ({helper}) => {367      const owner = await helper.eth.createAccountWithBalance(donor);368      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();369      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);370371      await expect(collectionHelper.methods372        .destroyCollection(collectionAddress)373        .send({from: owner})).to.be.fulfilled;374375      expect(await collectionHelper.methods376        .isCollectionExist(collectionAddress)377        .call()).to.be.false;378      expect(await helper.collection.getData(collectionId)).to.be.null;379    });380381    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {382      const owner = await helper.eth.createAccountWithBalance(donor);383      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);384      {385        const MAX_NAME_LENGTH = 64;386        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);387        const description = 'A';388        const tokenPrefix = 'A';389390        await expect(collectionHelper.methods391          .createCollection([392            collectionName,393            description,394            tokenPrefix,395            CollectionMode.Refungible,396            DECIMALS,397            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,398          ])399          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);400      }401      {402        const MAX_DESCRIPTION_LENGTH = 256;403        const collectionName = 'A';404        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);405        const tokenPrefix = 'A';406        await expect(collectionHelper.methods407          .createCollection([408            collectionName,409            description,410            tokenPrefix,411            CollectionMode.Refungible,412            DECIMALS,413            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,414          ])415          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);416      }417      {418        const MAX_TOKEN_PREFIX_LENGTH = 16;419        const collectionName = 'A';420        const description = 'A';421        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);422        await expect(collectionHelper.methods423          .createCollection([424            collectionName,425            description,426            tokenPrefix,427            CollectionMode.Refungible,428            DECIMALS,429            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,430          ])431          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);432      }433    });434435    itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {436      const owner = await helper.eth.createAccountWithBalance(donor);437      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);438      await expect(collectionHelper.methods439        .createCollection([440          'Peasantry',441          'absolutely anything',442          'TWIW',443          CollectionMode.Refungible,444          0,445          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,446        ])447        .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');448    });449  });450451  describe('Sponsoring', () => {452    itEth('Сan remove collection sponsor', async ({helper}) => {453      const owner = await helper.eth.createAccountWithBalance(donor);454      const sponsor = await helper.eth.createAccountWithBalance(donor);455      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);456457      const {collectionAddress} = await helper.eth.createCollection(458        owner,459        {460          ...CREATE_COLLECTION_DATA_DEFAULTS,461          name: 'Sponsor collection',462          description: '1',463          tokenPrefix: '1',464          collectionMode: 'nft',465          pendingSponsor: sponsorCross,466        },467      ).send();468      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);469470      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;471472      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});473      let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});474      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));475      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;476477      await collectionEvm.methods.removeCollectionSponsor().send({from: owner});478479      sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});480      expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');481    });482483    itEth('Can sponsor from evm address via access list', async ({helper}) => {484      const owner = await helper.eth.createAccountWithBalance(donor);485      const sponsorEth = await helper.eth.createAccountWithBalance(donor);486      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);487488      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(489        owner,490        {491          ...CREATE_COLLECTION_DATA_DEFAULTS,492          name: 'Sponsor collection',493          description: '1',494          tokenPrefix: '1',495          collectionMode: 'nft',496          pendingSponsor: sponsorCross,497          limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],498          tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],499        },500        '',501      );502503      const collectionSub = helper.nft.getCollectionObject(collectionId);504      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);505506      let sponsorship = (await collectionSub.getData())!.raw.sponsorship;507      expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));508      // Account cannot confirm sponsorship if it is not set as a sponsor509      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');510511      // Sponsor can confirm sponsorship:512      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});513      sponsorship = (await collectionSub.getData())!.raw.sponsorship;514      expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));515516      // Create user with no balance:517      const user = helper.ethCrossAccount.createAccount();518      const nextTokenId = await collectionEvm.methods.nextTokenId().call();519      expect(nextTokenId).to.be.equal('1');520521      // Set collection permissions:522      const oldPermissions = (await collectionSub.getData())!.raw.permissions;523      expect(oldPermissions.mintMode).to.be.false;524      expect(oldPermissions.access).to.be.equal('Normal');525526      await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});527      await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});528      await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});529530      const newPermissions = (await collectionSub.getData())!.raw.permissions;531      expect(newPermissions.mintMode).to.be.true;532      expect(newPermissions.access).to.be.equal('AllowList');533534      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));535      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));536      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));537538      // User can mint token without balance:539      {540        const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});541        const event = helper.eth.normalizeEvents(result.events)542          .find(event => event.event === 'Transfer');543544        expect(event).to.be.deep.equal({545          address: collectionAddress,546          event: 'Transfer',547          args: {548            from: '0x0000000000000000000000000000000000000000',549            to: user.eth,550            tokenId: '1',551          },552        });553554        // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});555556        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));557        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));558        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));559560        expect(await collectionEvm.methods.properties(nextTokenId, []).call())561          .to.be.like([562            [563              'key',564              '0x' + Buffer.from('Value').toString('hex'),565            ],566          ]);567        expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);568        expect(userBalanceAfter).to.be.eq(userBalanceBefore);569        expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;570      }571    });572573    itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {574      const owner = await helper.eth.createAccountWithBalance(donor);575      const sponsor = await helper.eth.createAccountWithBalance(donor);576      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);577      const user = helper.eth.createAccount();578      const userCross = helper.ethCrossAccount.fromAddress(user);579580      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(581        owner,582        {583          ...CREATE_COLLECTION_DATA_DEFAULTS,584          name: 'Sponsor collection',585          description: '1',586          tokenPrefix: '1',587          collectionMode: 'nft',588          pendingSponsor: sponsorCross,589          adminList: [userCross],590        },591        '',592      );593594      const collectionSub = helper.nft.getCollectionObject(collectionId);595      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);596      // Set collection sponsor:597      let collectionData = (await collectionSub.getData())!;598      expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));599      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');600601      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});602      collectionData = (await collectionSub.getData())!;603      expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));604605      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));606      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));607608      const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});609      const tokenId = mintingResult.events.Transfer.returnValues.tokenId;610611      const event = helper.eth.normalizeEvents(mintingResult.events)612        .find(event => event.event === 'Transfer');613      const address = helper.ethAddress.fromCollectionId(collectionId);614615      expect(event).to.be.deep.equal({616        address,617        event: 'Transfer',618        args: {619          from: '0x0000000000000000000000000000000000000000',620          to: user,621          tokenId: '1',622        },623      });624      expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');625626      const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));627      expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);628      const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));629      expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;630    });631632    itEth('Can reassign collection sponsor', async ({helper}) => {633      const owner = await helper.eth.createAccountWithBalance(donor);634      const sponsorEth = await helper.eth.createAccountWithBalance(donor);635      const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);636      const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);637      const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);638639      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(640        owner,641        {642          ...CREATE_COLLECTION_DATA_DEFAULTS,643          name: 'Sponsor collection',644          description: '1',645          tokenPrefix: '1',646          collectionMode: 'nft',647          pendingSponsor: sponsorCrossEth,648        },649        '',650      );651      const collectionSub = helper.nft.getCollectionObject(collectionId);652      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);653654      // Set and confirm sponsor:655      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});656657      // Can reassign sponsor:658      await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});659      const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;660      expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});661    });662663    [664      'transfer',665      'transferCross',666      'transferFrom',667      'transferFromCross',668    ].map(testCase =>669      itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {670        const owner = await helper.eth.createAccountWithBalance(donor);671        const sponsor = await helper.eth.createAccountWithBalance(donor);672        const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);673        const user = await helper.eth.createAccountWithBalance(donor);674        const userCross = helper.ethCrossAccount.fromAddress(user);675676        const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(677          owner,678          {679            ...CREATE_COLLECTION_DATA_DEFAULTS,680            name: 'Sponsor collection',681            description: '1',682            tokenPrefix: '1',683            collectionMode: 'rft',684            pendingSponsor: sponsorCross,685            adminList: [userCross],686          },687          '',688        );689        const receiver = await helper.eth.createAccountWithBalance(donor);690        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);691692        await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});693694        const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});695        const tokenId = result.events.Transfer.returnValues.tokenId;696697        const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));698        const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));699        const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));700701        switch (testCase) {702          case 'transfer':703            await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});704            break;705          case 'transferCross':706            await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});707            break;708          case 'transferFrom':709            await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});710            break;711          case 'transferFromCross':712            await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});713            break;714        }715716        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));717        expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);718        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));719        expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;720        const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));721        expect(userBalanceAfter).to.be.eq(userBalanceBefore);722      }));723  });724725  describe('Collection admins', () => {726    let donor: IKeyringPair;727728    before(async function() {729      await usingEthPlaygrounds(async (_helper, privateKey) => {730        donor = await privateKey({url: import.meta.url});731      });732    });733734    [735      {mode: 'nft' as const, requiredPallets: []},736      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},737      {mode: 'ft' as const, requiredPallets: []},738    ].map(testCase => {739      itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {740        // arrange741        const owner = await helper.eth.createAccountWithBalance(donor);742        const adminSub = await privateKey('//admin2');743        const adminEth = helper.eth.createAccount().toLowerCase();744745        const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);746        const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);747748        const {collectionAddress, collectionId} = await helper.eth.createCollection(749          owner,750          {751            ...CREATE_COLLECTION_DATA_DEFAULTS,752            name: 'Mint collection',753            description: 'a',754            tokenPrefix: 'b',755            collectionMode: testCase.mode,756            adminList: [adminCrossSub, adminCrossEth],757          },758        ).send();759        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);760761        // 1. Expect api.rpc.unique.adminlist returns admins:762        const adminListRpc = await helper.collection.getAdmins(collectionId);763        expect(adminListRpc).to.has.length(2);764        expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);765766        // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist767        let adminListEth = await collectionEvm.methods.collectionAdmins().call();768        adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));769        expect(adminListRpc).to.be.like(adminListEth);770771        // 3. check isOwnerOrAdminCross returns true:772        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;773        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;774      });775    });776777    itEth('cross account admin can mint', async ({helper}) => {778      // arrange: create collection and accounts779      const owner = await helper.eth.createAccountWithBalance(donor);780      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();781      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);782      const [adminSub] = await helper.arrange.createAccounts([100n], donor);783      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);784      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(785        owner,786        {787          ...CREATE_COLLECTION_DATA_DEFAULTS,788          name: 'Mint collection',789          description: 'a',790          tokenPrefix: 'b',791          collectionMode: 'nft',792          adminList: [adminCrossSub, adminCrossEth],793        },794        'uri',795      );796      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);797798      // admin (sub and eth) can mint token:799      await collectionEvm.methods.mint(owner).send({from: adminEth});800      await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});801802      expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);803    });804805    itEth('cannot add invalid cross account admin', async ({helper}) => {806      const owner = await helper.eth.createAccountWithBalance(donor);807      const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);808809      const adminCross = {810        eth: helper.address.substrateToEth(admin.address),811        sub: admin.addressRaw,812      };813814      await expect(helper.eth.createCollection(815        owner,816        {817          ...CREATE_COLLECTION_DATA_DEFAULTS,818          name: 'A',819          description: 'B',820          tokenPrefix: 'C',821          collectionMode: 'nft',822          adminList: [adminCross],823        },824      ).call()).to.be.rejected;825    });826827    itEth('Remove [cross] admin by owner', async ({helper}) => {828      const owner = await helper.eth.createAccountWithBalance(donor);829830      const [adminSub] = await helper.arrange.createAccounts([10n], donor);831      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();832      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);833      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);834835      const {collectionAddress, collectionId} = await helper.eth.createCollection(836        owner,837        {838          ...CREATE_COLLECTION_DATA_DEFAULTS,839          name: 'A',840          description: 'B',841          tokenPrefix: 'C',842          collectionMode: 'nft',843          adminList: [adminCrossSub, adminCrossEth],844        },845      ).send();846847      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);848849      {850        const adminList = await helper.collection.getAdmins(collectionId);851        expect(adminList).to.deep.include({Substrate: adminSub.address});852        expect(adminList).to.deep.include({Ethereum: adminEth});853      }854855      await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();856      await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();857      const adminList = await helper.collection.getAdmins(collectionId);858      expect(adminList.length).to.be.eq(0);859860      // Non admin cannot mint:861      await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);862      await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;863    });864  });865866  describe('Collection limits', () => {867    describe('Can set collection limits', () => {868      [869        {case: 'nft' as const},870        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},871        {case: 'ft' as const},872      ].map(testCase =>873        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {874          const owner = await helper.eth.createAccountWithBalance(donor);875876          const limits = {877            accountTokenOwnershipLimit: 1000n,878            sponsoredDataSize: 1024n,879            sponsoredDataRateLimit: 30n,880            tokenLimit: 1000000n,881            sponsorTransferTimeout: 6n,882            sponsorApproveTimeout: 6n,883            ownerCanTransfer: 1n,884            ownerCanDestroy: 0n,885            transfersEnabled: 0n,886          };887888          const {collectionId, collectionAddress} = await helper.eth.createCollection(889            owner,890            {891              ...CREATE_COLLECTION_DATA_DEFAULTS,892              name: 'Limits',893              description: 'absolutely anything',894              tokenPrefix: 'FLO',895              collectionMode: testCase.case,896              limits: [897                {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},898                {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},899                {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},900                {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},901                {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},902                {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},903                {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},904                {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},905                {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},906              ],907            },908          ).send();909910          const expectedLimits = {911            accountTokenOwnershipLimit: 1000,912            sponsoredDataSize: 1024,913            sponsoredDataRateLimit: {blocks: 30},914            tokenLimit: 1000000,915            sponsorTransferTimeout: 6,916            sponsorApproveTimeout: 6,917            ownerCanTransfer: true,918            ownerCanDestroy: false,919            transfersEnabled: false,920          };921922          const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);923924          // Check limits from sub:925          const data = (await helper.rft.getData(collectionId))!;926          expect(data.raw.limits).to.deep.eq(expectedLimits);927          expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);928          // Check limits from eth:929          const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});930          expect(limitsEvm).to.have.length(9);931          expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);932          expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);933          expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);934          expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);935          expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);936          expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);937          expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);938          expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);939          expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);940        }));941    });942943    describe('(!negative test!) Cannot set invalid collection limits', () => {944      [945        {case: 'nft' as const},946        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},947        {case: 'ft' as const},948      ].map(testCase =>949        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {950          const invalidLimits = {951            accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),952            transfersEnabled: 3,953          };954955          const createCollectionData = {956            ...CREATE_COLLECTION_DATA_DEFAULTS,957            name: 'Limits',958            description: 'absolutely anything',959            tokenPrefix: 'ISNI',960            collectionMode: testCase.case,961          };962963          const owner = await helper.eth.createAccountWithBalance(donor);964          await expect(helper.eth.createCollection(965            owner,966            {967              ...createCollectionData,968              limits: [{field: 9 as CollectionLimitField, value: 1n}],969            },970          ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');971972          await expect(helper.eth.createCollection(973            owner,974            {975              ...createCollectionData,976              limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],977            },978          ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);979980          await expect(helper.eth.createCollection(981            owner,982            {983              ...createCollectionData,984              limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],985            },986          ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);987988          await expect(helper.eth.createCollection(989            owner,990            {991              ...createCollectionData,992              limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],993            },994          ).call()).to.be.rejectedWith('value out-of-bounds');995        }));996    });997  });998999  describe('Collection properties', () => {10001001    [1002      {mode: 'nft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1003      {mode: 'rft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1004      {mode: 'ft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1005    ].map(testCase =>1006      itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1007        const caller = await helper.eth.createAccountWithBalance(donor);1008        const callerCross = helper.ethCrossAccount.fromAddress(caller);1009        const owner = await helper.eth.createAccountWithBalance(donor);1010        const {collectionId, collectionAddress} = await helper.eth.createCollection(1011          owner,1012          {1013            ...CREATE_COLLECTION_DATA_DEFAULTS,1014            name: 'name',1015            description: 'test',1016            tokenPrefix: 'test',1017            collectionMode: testCase.mode,1018            adminList: [callerCross],1019            properties: testCase.methodParams,1020          },1021        ).send();10221023        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10241025        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1026        expect(raw.properties).to.deep.equal(testCase.expectedProps);10271028        // collectionProperties returns properties:1029        expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1030      }));10311032    [1033      {mode: 'nft' as const},1034      {mode: 'rft' as const},1035      {mode: 'ft' as const},1036    ].map(testCase =>1037      itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1038        const caller = await helper.eth.createAccountWithBalance(donor);1039        const callerCross = helper.ethCrossAccount.fromAddress(caller);1040        const owner = await helper.eth.createAccountWithBalance(donor);1041        const {collectionId, collectionAddress} = await helper.eth.createCollection(1042          owner,1043          {1044            ...CREATE_COLLECTION_DATA_DEFAULTS,1045            name: 'name',1046            description: 'test',1047            tokenPrefix: 'test',1048            collectionMode: testCase.mode,1049            adminList: [callerCross],1050            properties:[1051              {key: 'testKey1', value: Buffer.from('testValue1')},1052              {key: 'testKey2', value: Buffer.from('testValue2')},1053              {key: 'testKey3', value: Buffer.from('testValue3')}],1054          },1055        ).send();10561057        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10581059        await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10601061        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10621063        expect(raw.properties.length).to.equal(1);1064        expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1065      }));10661067    itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1068      const caller = await helper.eth.createAccountWithBalance(donor);1069      const callerCross = helper.ethCrossAccount.fromAddress(caller);1070      const owner = await helper.eth.createAccountWithBalance(donor);1071      const createCollectionData = {1072        ...CREATE_COLLECTION_DATA_DEFAULTS,1073        name: 'name',1074        description: 'test',1075        tokenPrefix: 'test',1076        collectionMode: 'nft' as TCollectionMode,1077        adminList: [callerCross],1078      };1079      await expect(helper.eth.createCollection(1080        owner,1081        {1082          ...createCollectionData,1083          properties: [{key: '', value: Buffer.from('val1')}],1084        },1085      ).call()).to.be.rejected;10861087      await expect(helper.eth.createCollection(1088        owner,1089        {1090          ...createCollectionData,1091          properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1092        },1093      ).call()).to.be.rejected;10941095      await expect(helper.eth.createCollection(1096        owner,1097        {1098          ...createCollectionData,1099          properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1100        },1101      ).call()).to.be.rejected;1102    });11031104    itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1105      const caller = await helper.eth.createAccountWithBalance(donor);1106      const owner = await helper.eth.createAccountWithBalance(donor);1107      const {collectionAddress} = await helper.eth.createCollection(1108        owner,1109        {1110          ...CREATE_COLLECTION_DATA_DEFAULTS,1111          name: 'name',1112          description: 'test',1113          tokenPrefix: 'test',1114          collectionMode: 'nft',1115          properties:[1116            {key: 'testKey1', value: Buffer.from('testValue1')},1117            {key: 'testKey2', value: Buffer.from('testValue2')}],1118        },1119      ).send();11201121      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11221123      await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1124    });1125  });11261127  describe('Token property permissions', () => {1128    [1129      {mode: 'nft' as const, requiredPallets: []},1130      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1131    ].map(testCase =>1132      itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1133        const owner = await helper.eth.createAccountWithBalance(donor);1134        const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1135        for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1136          const {collectionId, collectionAddress} = await helper.eth.createCollection(1137            owner,1138            {1139              ...CREATE_COLLECTION_DATA_DEFAULTS,1140              name: 'A',1141              description: 'B',1142              tokenPrefix: 'C',1143              collectionMode: testCase.mode,1144              adminList: [caller],1145              tokenPropertyPermissions: [1146                {1147                  key: 'testKey',1148                  permissions: [1149                    {code: TokenPermissionField.Mutable, value: mutable},1150                    {code: TokenPermissionField.TokenOwner, value: tokenOwner},1151                    {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1152                  ],1153                },1154              ],1155            },1156          ).send();1157          const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11581159          expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1160            key: 'testKey',1161            permission: {mutable, collectionAdmin, tokenOwner},1162          }]);11631164          expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1165            ['testKey', [1166              [TokenPermissionField.Mutable.toString(), mutable],1167              [TokenPermissionField.TokenOwner.toString(), tokenOwner],1168              [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1169            ],1170          ]);1171        }1172      }));11731174    [1175      {mode: 'nft' as const, requiredPallets: []},1176      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1177    ].map(testCase =>1178      itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1179        const owner = await helper.eth.createAccountWithBalance(donor);1180        const {collectionId, collectionAddress} = await helper.eth.createCollection(1181          owner,1182          {1183            ...CREATE_COLLECTION_DATA_DEFAULTS,1184            name: 'A',1185            description: 'B',1186            tokenPrefix: 'C',1187            collectionMode: testCase.mode,1188            tokenPropertyPermissions: [1189              {1190                key: 'testKey_0',1191                permissions: [1192                  {code: TokenPermissionField.Mutable, value: true},1193                  {code: TokenPermissionField.TokenOwner, value: true},1194                  {code: TokenPermissionField.CollectionAdmin, value: true}],1195              },1196              {1197                key: 'testKey_1',1198                permissions: [1199                  {code: TokenPermissionField.Mutable, value: true},1200                  {code: TokenPermissionField.TokenOwner, value: false},1201                  {code: TokenPermissionField.CollectionAdmin, value: true}],1202              },1203              {1204                key: 'testKey_2',1205                permissions: [1206                  {code: TokenPermissionField.Mutable, value: false},1207                  {code: TokenPermissionField.TokenOwner, value: true},1208                  {code: TokenPermissionField.CollectionAdmin, value: false}],1209              },1210            ],1211          },1212        ).send();1213        const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12141215        expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1216          {1217            key: 'testKey_0',1218            permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1219          },1220          {1221            key: 'testKey_1',1222            permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1223          },1224          {1225            key: 'testKey_2',1226            permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1227          },1228        ]);12291230        expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1231          ['testKey_0', [1232            [TokenPermissionField.Mutable.toString(), true],1233            [TokenPermissionField.TokenOwner.toString(), true],1234            [TokenPermissionField.CollectionAdmin.toString(), true]],1235          ],1236          ['testKey_1', [1237            [TokenPermissionField.Mutable.toString(), true],1238            [TokenPermissionField.TokenOwner.toString(), false],1239            [TokenPermissionField.CollectionAdmin.toString(), true]],1240          ],1241          ['testKey_2', [1242            [TokenPermissionField.Mutable.toString(), false],1243            [TokenPermissionField.TokenOwner.toString(), true],1244            [TokenPermissionField.CollectionAdmin.toString(), false]],1245          ],1246        ]);1247      }));12481249    [1250      {mode: 'nft' as const, requiredPallets: []},1251      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1252    ].map(testCase =>1253      itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1254        const caller = await helper.eth.createAccountWithBalance(donor);1255        const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1256        const {collectionAddress} = await helper.eth.createCollection(1257          caller,1258          {1259            ...CREATE_COLLECTION_DATA_DEFAULTS,1260            name: 'A',1261            description: 'B',1262            tokenPrefix: 'C',1263            collectionMode: testCase.mode,1264            adminList: [receiver],1265            tokenPropertyPermissions: [1266              {1267                key: 'testKey',1268                permissions: [1269                  {code: TokenPermissionField.Mutable, value: true},1270                  {code: TokenPermissionField.CollectionAdmin, value: true}],1271              },1272              {1273                key: 'testKey_1',1274                permissions: [1275                  {code: TokenPermissionField.Mutable, value: true},1276                  {code: TokenPermissionField.CollectionAdmin, value: true}],1277              },1278            ],1279          },1280        ).send();12811282        const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1283        const tokenId = (await collection.methods.mintCross(receiver, [{key: 'testKey', value: Buffer.from('testValue')}, {key: 'testKey_1', value: Buffer.from('testValue_1')}]).send()).events.Transfer.returnValues.tokenId;1284        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12851286        await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1287        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1288      }));1289  });12901291  describe('Nesting', () => {1292    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1293      const owner = await helper.eth.createAccountWithBalance(donor);1294      const {collectionAddress, collectionId} = await helper.eth.createCollection(1295        owner,1296        {1297          ...CREATE_COLLECTION_DATA_DEFAULTS,1298          name: 'A',1299          description: 'B',1300          tokenPrefix: 'C',1301          collectionMode: 'nft',1302          nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1303        },1304      ).send();13051306      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13071308      // Create a token to be nested to1309      const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1310      const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1311      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13121313      // Create a nested token1314      const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1315      const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1316      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13171318      // Create a token to be nested and nest1319      const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1320      const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13211322      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1323      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13241325      // Unnest token back1326      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1327      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1328    });13291330    itEth('NFT: collectionNesting()', async ({helper}) => {1331      const owner = await helper.eth.createAccountWithBalance(donor);1332      const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1333        owner,1334        new CreateCollectionData('A', 'B', 'C', 'nft'),1335      ).send();13361337      const unnestedContract = helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1338      expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13391340      const {collectionAddress} = await helper.eth.createCollection(1341        owner,1342        {1343          ...CREATE_COLLECTION_DATA_DEFAULTS,1344          name: 'A',1345          description: 'B',1346          tokenPrefix: 'C',1347          collectionMode: 'nft',1348          nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1349        },1350      ).send();13511352      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1353      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1354      await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1355      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1356    });13571358    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1359      const owner = await helper.eth.createAccountWithBalance(donor);13601361      const {collectionId, collectionAddress} = await helper.eth.createCollection(1362        owner,1363        {1364          ...CREATE_COLLECTION_DATA_DEFAULTS,1365          name: 'A',1366          description: 'B',1367          tokenPrefix: 'C',1368          collectionMode: 'nft',1369          nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1370        },1371      ).send();13721373      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13741375      // Create a token to nest into1376      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1377      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1378      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13791380      // Create a token to nest1381      const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1382      const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13831384      // Try to nest1385      await expect(contract.methods1386        .transfer(targetNftTokenAddress, nftTokenId)1387        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1388    });1389  });13901391  describe('Flags', () => {1392    const createCollectionData = {1393      ...CREATE_COLLECTION_DATA_DEFAULTS,1394      name: 'A',1395      description: 'B',1396      tokenPrefix: 'C',1397      collectionMode: 'nft' as TCollectionMode,1398    };13991400    itEth('NFT: use numbers for flags', async ({helper}) => {1401      const owner = await helper.eth.createAccountWithBalance(donor);14021403      {1404        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1405        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1406      }14071408      {1409        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1410        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1411      }1412    });14131414    itEth('NFT: can\'t set foreign flag number', async ({helper}) => {1415      const owner = await helper.eth.createAccountWithBalance(donor);14161417      {1418        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1419      }14201421      {1422        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1423      }1424    });14251426    itEth('NFT: use enum for flags', async ({helper}) => {1427      const owner = await helper.eth.createAccountWithBalance(donor);14281429      {1430        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1431        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1432      }1433    });14341435    itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1436      const owner = await helper.eth.createAccountWithBalance(donor);14371438      {1439        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1440      }14411442      {1443        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1444      }1445    });1446  });1447});
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -278,6 +278,7 @@
   }
 
   async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
+    if(!wsEndpoint) throw new Error('wsEndpoint was not set');
     const wsProvider = new WsProvider(wsEndpoint);
     this.api = new ApiPromise({
       provider: wsProvider,
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -492,6 +492,7 @@
   }
 
   static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {
+    if(!wsEndpoint) throw new Error('wsEndpoint was not set');
     const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});
     await api.isReady;
 
@@ -507,6 +508,7 @@
     network: TNetworks;
   }> {
     if(typeof network === 'undefined' || network === null) network = 'opal';
+    if(!wsEndpoint) throw new Error('wsEndpoint was not set');
     const supportedRPC = {
       opal: {
         unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,