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});
after · 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      // Parallel test safety221      expect(collectionCountAfter - collectionCountBefore).to.be.gte(1);222      expect(collectionId).to.be.eq(collectionCountAfter);223      expect(data.name).to.be.eq(name);224      expect(data.description).to.be.eq(description);225      expect(data.raw.tokenPrefix).to.be.eq(prefix);226      expect(data.raw.mode).to.be.eq('NFT');227228      const options = await collection.getOptions();229230      expect(options.tokenPropertyPermissions).to.be.empty;231    });232233    // this test will occasionally fail when in async environment.234    itEth('Check collection address exist', async ({helper}) => {235      const owner = await helper.eth.createAccountWithBalance(donor);236237      const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;238      const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);239      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);240241      expect(await collectionHelpers.methods242        .isCollectionExist(expectedCollectionAddress)243        .call()).to.be.false;244245      await collectionHelpers.methods246        .createCollection([247          'A',248          'A',249          'A',250          CollectionMode.Nonfungible,251          0,252          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,253        ])254        .send({value: Number(2n * helper.balance.getOneTokenNominal())});255256      expect(await collectionHelpers.methods257        .isCollectionExist(expectedCollectionAddress)258        .call()).to.be.true;259    });260  });261262  describe('Create RFT collection from EVM', () => {263    before(async function() {264      await usingEthPlaygrounds((helper) => {265        requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);266        return Promise.resolve();267      });268    });269270    itEth('Create collection', async ({helper}) => {271      const owner = await helper.eth.createAccountWithBalance(donor);272273      const name = 'CollectionEVM';274      const description = 'Some description';275      const prefix = 'token prefix';276277      const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();278      const data = (await helper.rft.getData(collectionId))!;279      const collection = helper.rft.getCollectionObject(collectionId);280281      expect(data.name).to.be.eq(name);282      expect(data.description).to.be.eq(description);283      expect(data.raw.tokenPrefix).to.be.eq(prefix);284      expect(data.raw.mode).to.be.eq('ReFungible');285286      const options = await collection.getOptions();287288      expect(options.tokenPropertyPermissions).to.be.empty;289    });290291    itEth('Create collection with properties & get description', async ({helper}) => {292      const owner = await helper.eth.createAccountWithBalance(donor);293294      const name = 'CollectionEVM';295      const description = 'Some description';296      const prefix = 'token prefix';297      const baseUri = 'BaseURI';298299      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);300      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');301302      const collection = helper.rft.getCollectionObject(collectionId);303      const data = (await collection.getData())!;304305      expect(data.name).to.be.eq(name);306      expect(data.description).to.be.eq(description);307      expect(data.raw.tokenPrefix).to.be.eq(prefix);308      expect(data.raw.mode).to.be.eq('ReFungible');309310      expect(await contract.methods.description().call()).to.deep.equal(description);311312      const options = await collection.getOptions();313      expect(options.tokenPropertyPermissions).to.be.deep.equal([314        {315          key: 'URI',316          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},317        },318        {319          key: 'URISuffix',320          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},321        },322      ]);323    });324325    itEth('Set sponsorship', async ({helper}) => {326      const owner = await helper.eth.createAccountWithBalance(donor);327      const sponsor = await helper.eth.createAccountWithBalance(donor);328      const ss58Format = helper.chain.getChainProperties().ss58Format;329      const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();330331      const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);332      const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);333      await collection.methods.setCollectionSponsorCross(sponsorCross).send();334335      let data = (await helper.rft.getData(collectionId))!;336      expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));337338      await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');339340      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);341      await sponsorCollection.methods.confirmCollectionSponsorship().send();342343      data = (await helper.rft.getData(collectionId))!;344      expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));345    });346347    itEth('Collection address exist', async ({helper}) => {348      const owner = await helper.eth.createAccountWithBalance(donor);349      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';350      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);351352      expect(await collectionHelpers353        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())354        .to.be.false;355356      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();357      expect(await collectionHelpers358        .methods.isCollectionExist(collectionAddress).call())359        .to.be.true;360361      // check collectionOwner:362      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);363      const collectionOwner = await collectionEvm.methods.collectionOwner().call();364      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));365    });366367    itEth('destroyCollection', async ({helper}) => {368      const owner = await helper.eth.createAccountWithBalance(donor);369      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();370      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);371372      await expect(collectionHelper.methods373        .destroyCollection(collectionAddress)374        .send({from: owner})).to.be.fulfilled;375376      expect(await collectionHelper.methods377        .isCollectionExist(collectionAddress)378        .call()).to.be.false;379      expect(await helper.collection.getData(collectionId)).to.be.null;380    });381382    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {383      const owner = await helper.eth.createAccountWithBalance(donor);384      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);385      {386        const MAX_NAME_LENGTH = 64;387        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);388        const description = 'A';389        const tokenPrefix = 'A';390391        await expect(collectionHelper.methods392          .createCollection([393            collectionName,394            description,395            tokenPrefix,396            CollectionMode.Refungible,397            DECIMALS,398            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,399          ])400          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);401      }402      {403        const MAX_DESCRIPTION_LENGTH = 256;404        const collectionName = 'A';405        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);406        const tokenPrefix = 'A';407        await expect(collectionHelper.methods408          .createCollection([409            collectionName,410            description,411            tokenPrefix,412            CollectionMode.Refungible,413            DECIMALS,414            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,415          ])416          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);417      }418      {419        const MAX_TOKEN_PREFIX_LENGTH = 16;420        const collectionName = 'A';421        const description = 'A';422        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);423        await expect(collectionHelper.methods424          .createCollection([425            collectionName,426            description,427            tokenPrefix,428            CollectionMode.Refungible,429            DECIMALS,430            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,431          ])432          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);433      }434    });435436    itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {437      const owner = await helper.eth.createAccountWithBalance(donor);438      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);439      await expect(collectionHelper.methods440        .createCollection([441          'Peasantry',442          'absolutely anything',443          'TWIW',444          CollectionMode.Refungible,445          0,446          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,447        ])448        .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');449    });450  });451452  describe('Sponsoring', () => {453    itEth('Сan remove collection sponsor', async ({helper}) => {454      const owner = await helper.eth.createAccountWithBalance(donor);455      const sponsor = await helper.eth.createAccountWithBalance(donor);456      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);457458      const {collectionAddress} = await helper.eth.createCollection(459        owner,460        {461          ...CREATE_COLLECTION_DATA_DEFAULTS,462          name: 'Sponsor collection',463          description: '1',464          tokenPrefix: '1',465          collectionMode: 'nft',466          pendingSponsor: sponsorCross,467        },468      ).send();469      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);470471      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;472473      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});474      let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});475      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));476      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;477478      await collectionEvm.methods.removeCollectionSponsor().send({from: owner});479480      sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});481      expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');482    });483484    itEth('Can sponsor from evm address via access list', async ({helper}) => {485      const owner = await helper.eth.createAccountWithBalance(donor);486      const sponsorEth = await helper.eth.createAccountWithBalance(donor);487      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);488489      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(490        owner,491        {492          ...CREATE_COLLECTION_DATA_DEFAULTS,493          name: 'Sponsor collection',494          description: '1',495          tokenPrefix: '1',496          collectionMode: 'nft',497          pendingSponsor: sponsorCross,498          limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],499          tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],500        },501        '',502      );503504      const collectionSub = helper.nft.getCollectionObject(collectionId);505      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);506507      let sponsorship = (await collectionSub.getData())!.raw.sponsorship;508      expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));509      // Account cannot confirm sponsorship if it is not set as a sponsor510      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');511512      // Sponsor can confirm sponsorship:513      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});514      sponsorship = (await collectionSub.getData())!.raw.sponsorship;515      expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));516517      // Create user with no balance:518      const user = helper.ethCrossAccount.createAccount();519      const nextTokenId = await collectionEvm.methods.nextTokenId().call();520      expect(nextTokenId).to.be.equal('1');521522      // Set collection permissions:523      const oldPermissions = (await collectionSub.getData())!.raw.permissions;524      expect(oldPermissions.mintMode).to.be.false;525      expect(oldPermissions.access).to.be.equal('Normal');526527      await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});528      await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});529      await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});530531      const newPermissions = (await collectionSub.getData())!.raw.permissions;532      expect(newPermissions.mintMode).to.be.true;533      expect(newPermissions.access).to.be.equal('AllowList');534535      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));536      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));537      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));538539      // User can mint token without balance:540      {541        const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});542        const event = helper.eth.normalizeEvents(result.events)543          .find(event => event.event === 'Transfer');544545        expect(event).to.be.deep.equal({546          address: collectionAddress,547          event: 'Transfer',548          args: {549            from: '0x0000000000000000000000000000000000000000',550            to: user.eth,551            tokenId: '1',552          },553        });554555        // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});556557        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));558        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));559        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));560561        expect(await collectionEvm.methods.properties(nextTokenId, []).call())562          .to.be.like([563            [564              'key',565              '0x' + Buffer.from('Value').toString('hex'),566            ],567          ]);568        expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);569        expect(userBalanceAfter).to.be.eq(userBalanceBefore);570        expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;571      }572    });573574    itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {575      const owner = await helper.eth.createAccountWithBalance(donor);576      const sponsor = await helper.eth.createAccountWithBalance(donor);577      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);578      const user = helper.eth.createAccount();579      const userCross = helper.ethCrossAccount.fromAddress(user);580581      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(582        owner,583        {584          ...CREATE_COLLECTION_DATA_DEFAULTS,585          name: 'Sponsor collection',586          description: '1',587          tokenPrefix: '1',588          collectionMode: 'nft',589          pendingSponsor: sponsorCross,590          adminList: [userCross],591        },592        '',593      );594595      const collectionSub = helper.nft.getCollectionObject(collectionId);596      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);597      // Set collection sponsor:598      let collectionData = (await collectionSub.getData())!;599      expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));600      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');601602      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});603      collectionData = (await collectionSub.getData())!;604      expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));605606      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));607      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));608609      const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});610      const tokenId = mintingResult.events.Transfer.returnValues.tokenId;611612      const event = helper.eth.normalizeEvents(mintingResult.events)613        .find(event => event.event === 'Transfer');614      const address = helper.ethAddress.fromCollectionId(collectionId);615616      expect(event).to.be.deep.equal({617        address,618        event: 'Transfer',619        args: {620          from: '0x0000000000000000000000000000000000000000',621          to: user,622          tokenId: '1',623        },624      });625      expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');626627      const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));628      expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);629      const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));630      expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;631    });632633    itEth('Can reassign collection sponsor', async ({helper}) => {634      const owner = await helper.eth.createAccountWithBalance(donor);635      const sponsorEth = await helper.eth.createAccountWithBalance(donor);636      const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);637      const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);638      const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);639640      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(641        owner,642        {643          ...CREATE_COLLECTION_DATA_DEFAULTS,644          name: 'Sponsor collection',645          description: '1',646          tokenPrefix: '1',647          collectionMode: 'nft',648          pendingSponsor: sponsorCrossEth,649        },650        '',651      );652      const collectionSub = helper.nft.getCollectionObject(collectionId);653      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);654655      // Set and confirm sponsor:656      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});657658      // Can reassign sponsor:659      await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});660      const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;661      expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});662    });663664    [665      'transfer',666      'transferCross',667      'transferFrom',668      'transferFromCross',669    ].map(testCase =>670      itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {671        const owner = await helper.eth.createAccountWithBalance(donor);672        const sponsor = await helper.eth.createAccountWithBalance(donor);673        const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);674        const user = await helper.eth.createAccountWithBalance(donor);675        const userCross = helper.ethCrossAccount.fromAddress(user);676677        const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(678          owner,679          {680            ...CREATE_COLLECTION_DATA_DEFAULTS,681            name: 'Sponsor collection',682            description: '1',683            tokenPrefix: '1',684            collectionMode: 'rft',685            pendingSponsor: sponsorCross,686            adminList: [userCross],687          },688          '',689        );690        const receiver = await helper.eth.createAccountWithBalance(donor);691        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);692693        await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});694695        const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});696        const tokenId = result.events.Transfer.returnValues.tokenId;697698        const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));699        const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));700        const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));701702        switch (testCase) {703          case 'transfer':704            await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});705            break;706          case 'transferCross':707            await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});708            break;709          case 'transferFrom':710            await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});711            break;712          case 'transferFromCross':713            await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});714            break;715        }716717        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));718        expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);719        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));720        expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;721        const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));722        expect(userBalanceAfter).to.be.eq(userBalanceBefore);723      }));724  });725726  describe('Collection admins', () => {727    let donor: IKeyringPair;728729    before(async function() {730      await usingEthPlaygrounds(async (_helper, privateKey) => {731        donor = await privateKey({url: import.meta.url});732      });733    });734735    [736      {mode: 'nft' as const, requiredPallets: []},737      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},738      {mode: 'ft' as const, requiredPallets: []},739    ].map(testCase => {740      itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {741        // arrange742        const owner = await helper.eth.createAccountWithBalance(donor);743        const adminSub = await privateKey('//admin2');744        const adminEth = helper.eth.createAccount().toLowerCase();745746        const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);747        const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);748749        const {collectionAddress, collectionId} = await helper.eth.createCollection(750          owner,751          {752            ...CREATE_COLLECTION_DATA_DEFAULTS,753            name: 'Mint collection',754            description: 'a',755            tokenPrefix: 'b',756            collectionMode: testCase.mode,757            adminList: [adminCrossSub, adminCrossEth],758          },759        ).send();760        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);761762        // 1. Expect api.rpc.unique.adminlist returns admins:763        const adminListRpc = await helper.collection.getAdmins(collectionId);764        expect(adminListRpc).to.has.length(2);765        expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);766767        // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist768        let adminListEth = await collectionEvm.methods.collectionAdmins().call();769        adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));770        expect(adminListRpc).to.be.like(adminListEth);771772        // 3. check isOwnerOrAdminCross returns true:773        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;774        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;775      });776    });777778    itEth('cross account admin can mint', async ({helper}) => {779      // arrange: create collection and accounts780      const owner = await helper.eth.createAccountWithBalance(donor);781      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();782      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);783      const [adminSub] = await helper.arrange.createAccounts([100n], donor);784      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);785      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(786        owner,787        {788          ...CREATE_COLLECTION_DATA_DEFAULTS,789          name: 'Mint collection',790          description: 'a',791          tokenPrefix: 'b',792          collectionMode: 'nft',793          adminList: [adminCrossSub, adminCrossEth],794        },795        'uri',796      );797      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);798799      // admin (sub and eth) can mint token:800      await collectionEvm.methods.mint(owner).send({from: adminEth});801      await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});802803      expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);804    });805806    itEth('cannot add invalid cross account admin', async ({helper}) => {807      const owner = await helper.eth.createAccountWithBalance(donor);808      const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);809810      const adminCross = {811        eth: helper.address.substrateToEth(admin.address),812        sub: admin.addressRaw,813      };814815      await expect(helper.eth.createCollection(816        owner,817        {818          ...CREATE_COLLECTION_DATA_DEFAULTS,819          name: 'A',820          description: 'B',821          tokenPrefix: 'C',822          collectionMode: 'nft',823          adminList: [adminCross],824        },825      ).call()).to.be.rejected;826    });827828    itEth('Remove [cross] admin by owner', async ({helper}) => {829      const owner = await helper.eth.createAccountWithBalance(donor);830831      const [adminSub] = await helper.arrange.createAccounts([10n], donor);832      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();833      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);834      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);835836      const {collectionAddress, collectionId} = await helper.eth.createCollection(837        owner,838        {839          ...CREATE_COLLECTION_DATA_DEFAULTS,840          name: 'A',841          description: 'B',842          tokenPrefix: 'C',843          collectionMode: 'nft',844          adminList: [adminCrossSub, adminCrossEth],845        },846      ).send();847848      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);849850      {851        const adminList = await helper.collection.getAdmins(collectionId);852        expect(adminList).to.deep.include({Substrate: adminSub.address});853        expect(adminList).to.deep.include({Ethereum: adminEth});854      }855856      await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();857      await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();858      const adminList = await helper.collection.getAdmins(collectionId);859      expect(adminList.length).to.be.eq(0);860861      // Non admin cannot mint:862      await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);863      await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;864    });865  });866867  describe('Collection limits', () => {868    describe('Can set collection limits', () => {869      [870        {case: 'nft' as const},871        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},872        {case: 'ft' as const},873      ].map(testCase =>874        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {875          const owner = await helper.eth.createAccountWithBalance(donor);876877          const limits = {878            accountTokenOwnershipLimit: 1000n,879            sponsoredDataSize: 1024n,880            sponsoredDataRateLimit: 30n,881            tokenLimit: 1000000n,882            sponsorTransferTimeout: 6n,883            sponsorApproveTimeout: 6n,884            ownerCanTransfer: 1n,885            ownerCanDestroy: 0n,886            transfersEnabled: 0n,887          };888889          const {collectionId, collectionAddress} = await helper.eth.createCollection(890            owner,891            {892              ...CREATE_COLLECTION_DATA_DEFAULTS,893              name: 'Limits',894              description: 'absolutely anything',895              tokenPrefix: 'FLO',896              collectionMode: testCase.case,897              limits: [898                {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},899                {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},900                {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},901                {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},902                {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},903                {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},904                {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},905                {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},906                {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},907              ],908            },909          ).send();910911          const expectedLimits = {912            accountTokenOwnershipLimit: 1000,913            sponsoredDataSize: 1024,914            sponsoredDataRateLimit: {blocks: 30},915            tokenLimit: 1000000,916            sponsorTransferTimeout: 6,917            sponsorApproveTimeout: 6,918            ownerCanTransfer: true,919            ownerCanDestroy: false,920            transfersEnabled: false,921          };922923          const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);924925          // Check limits from sub:926          const data = (await helper.rft.getData(collectionId))!;927          expect(data.raw.limits).to.deep.eq(expectedLimits);928          expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);929          // Check limits from eth:930          const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});931          expect(limitsEvm).to.have.length(9);932          expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);933          expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);934          expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);935          expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);936          expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);937          expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);938          expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);939          expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);940          expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);941        }));942    });943944    describe('(!negative test!) Cannot set invalid collection limits', () => {945      [946        {case: 'nft' as const},947        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},948        {case: 'ft' as const},949      ].map(testCase =>950        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {951          const invalidLimits = {952            accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),953            transfersEnabled: 3,954          };955956          const createCollectionData = {957            ...CREATE_COLLECTION_DATA_DEFAULTS,958            name: 'Limits',959            description: 'absolutely anything',960            tokenPrefix: 'ISNI',961            collectionMode: testCase.case,962          };963964          const owner = await helper.eth.createAccountWithBalance(donor);965          await expect(helper.eth.createCollection(966            owner,967            {968              ...createCollectionData,969              limits: [{field: 9 as CollectionLimitField, value: 1n}],970            },971          ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');972973          await expect(helper.eth.createCollection(974            owner,975            {976              ...createCollectionData,977              limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],978            },979          ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);980981          await expect(helper.eth.createCollection(982            owner,983            {984              ...createCollectionData,985              limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],986            },987          ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);988989          await expect(helper.eth.createCollection(990            owner,991            {992              ...createCollectionData,993              limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],994            },995          ).call()).to.be.rejectedWith('value out-of-bounds');996        }));997    });998  });9991000  describe('Collection properties', () => {10011002    [1003      {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'}]},1004      {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'}]},1005      {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'}]},1006    ].map(testCase =>1007      itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1008        const caller = await helper.eth.createAccountWithBalance(donor);1009        const callerCross = helper.ethCrossAccount.fromAddress(caller);1010        const owner = await helper.eth.createAccountWithBalance(donor);1011        const {collectionId, collectionAddress} = await helper.eth.createCollection(1012          owner,1013          {1014            ...CREATE_COLLECTION_DATA_DEFAULTS,1015            name: 'name',1016            description: 'test',1017            tokenPrefix: 'test',1018            collectionMode: testCase.mode,1019            adminList: [callerCross],1020            properties: testCase.methodParams,1021          },1022        ).send();10231024        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10251026        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1027        expect(raw.properties).to.deep.equal(testCase.expectedProps);10281029        // collectionProperties returns properties:1030        expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1031      }));10321033    [1034      {mode: 'nft' as const},1035      {mode: 'rft' as const},1036      {mode: 'ft' as const},1037    ].map(testCase =>1038      itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1039        const caller = await helper.eth.createAccountWithBalance(donor);1040        const callerCross = helper.ethCrossAccount.fromAddress(caller);1041        const owner = await helper.eth.createAccountWithBalance(donor);1042        const {collectionId, collectionAddress} = await helper.eth.createCollection(1043          owner,1044          {1045            ...CREATE_COLLECTION_DATA_DEFAULTS,1046            name: 'name',1047            description: 'test',1048            tokenPrefix: 'test',1049            collectionMode: testCase.mode,1050            adminList: [callerCross],1051            properties:[1052              {key: 'testKey1', value: Buffer.from('testValue1')},1053              {key: 'testKey2', value: Buffer.from('testValue2')},1054              {key: 'testKey3', value: Buffer.from('testValue3')}],1055          },1056        ).send();10571058        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10591060        await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10611062        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10631064        expect(raw.properties.length).to.equal(1);1065        expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1066      }));10671068    itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1069      const caller = await helper.eth.createAccountWithBalance(donor);1070      const callerCross = helper.ethCrossAccount.fromAddress(caller);1071      const owner = await helper.eth.createAccountWithBalance(donor);1072      const createCollectionData = {1073        ...CREATE_COLLECTION_DATA_DEFAULTS,1074        name: 'name',1075        description: 'test',1076        tokenPrefix: 'test',1077        collectionMode: 'nft' as TCollectionMode,1078        adminList: [callerCross],1079      };1080      await expect(helper.eth.createCollection(1081        owner,1082        {1083          ...createCollectionData,1084          properties: [{key: '', value: Buffer.from('val1')}],1085        },1086      ).call()).to.be.rejected;10871088      await expect(helper.eth.createCollection(1089        owner,1090        {1091          ...createCollectionData,1092          properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1093        },1094      ).call()).to.be.rejected;10951096      await expect(helper.eth.createCollection(1097        owner,1098        {1099          ...createCollectionData,1100          properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1101        },1102      ).call()).to.be.rejected;1103    });11041105    itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1106      const caller = await helper.eth.createAccountWithBalance(donor);1107      const owner = await helper.eth.createAccountWithBalance(donor);1108      const {collectionAddress} = await helper.eth.createCollection(1109        owner,1110        {1111          ...CREATE_COLLECTION_DATA_DEFAULTS,1112          name: 'name',1113          description: 'test',1114          tokenPrefix: 'test',1115          collectionMode: 'nft',1116          properties:[1117            {key: 'testKey1', value: Buffer.from('testValue1')},1118            {key: 'testKey2', value: Buffer.from('testValue2')}],1119        },1120      ).send();11211122      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11231124      await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1125    });1126  });11271128  describe('Token property permissions', () => {1129    [1130      {mode: 'nft' as const, requiredPallets: []},1131      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1132    ].map(testCase =>1133      itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1134        const owner = await helper.eth.createAccountWithBalance(donor);1135        const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1136        for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1137          const {collectionId, collectionAddress} = await helper.eth.createCollection(1138            owner,1139            {1140              ...CREATE_COLLECTION_DATA_DEFAULTS,1141              name: 'A',1142              description: 'B',1143              tokenPrefix: 'C',1144              collectionMode: testCase.mode,1145              adminList: [caller],1146              tokenPropertyPermissions: [1147                {1148                  key: 'testKey',1149                  permissions: [1150                    {code: TokenPermissionField.Mutable, value: mutable},1151                    {code: TokenPermissionField.TokenOwner, value: tokenOwner},1152                    {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1153                  ],1154                },1155              ],1156            },1157          ).send();1158          const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11591160          expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1161            key: 'testKey',1162            permission: {mutable, collectionAdmin, tokenOwner},1163          }]);11641165          expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1166            ['testKey', [1167              [TokenPermissionField.Mutable.toString(), mutable],1168              [TokenPermissionField.TokenOwner.toString(), tokenOwner],1169              [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1170            ],1171          ]);1172        }1173      }));11741175    [1176      {mode: 'nft' as const, requiredPallets: []},1177      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1178    ].map(testCase =>1179      itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1180        const owner = await helper.eth.createAccountWithBalance(donor);1181        const {collectionId, collectionAddress} = await helper.eth.createCollection(1182          owner,1183          {1184            ...CREATE_COLLECTION_DATA_DEFAULTS,1185            name: 'A',1186            description: 'B',1187            tokenPrefix: 'C',1188            collectionMode: testCase.mode,1189            tokenPropertyPermissions: [1190              {1191                key: 'testKey_0',1192                permissions: [1193                  {code: TokenPermissionField.Mutable, value: true},1194                  {code: TokenPermissionField.TokenOwner, value: true},1195                  {code: TokenPermissionField.CollectionAdmin, value: true}],1196              },1197              {1198                key: 'testKey_1',1199                permissions: [1200                  {code: TokenPermissionField.Mutable, value: true},1201                  {code: TokenPermissionField.TokenOwner, value: false},1202                  {code: TokenPermissionField.CollectionAdmin, value: true}],1203              },1204              {1205                key: 'testKey_2',1206                permissions: [1207                  {code: TokenPermissionField.Mutable, value: false},1208                  {code: TokenPermissionField.TokenOwner, value: true},1209                  {code: TokenPermissionField.CollectionAdmin, value: false}],1210              },1211            ],1212          },1213        ).send();1214        const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12151216        expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1217          {1218            key: 'testKey_0',1219            permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1220          },1221          {1222            key: 'testKey_1',1223            permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1224          },1225          {1226            key: 'testKey_2',1227            permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1228          },1229        ]);12301231        expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1232          ['testKey_0', [1233            [TokenPermissionField.Mutable.toString(), true],1234            [TokenPermissionField.TokenOwner.toString(), true],1235            [TokenPermissionField.CollectionAdmin.toString(), true]],1236          ],1237          ['testKey_1', [1238            [TokenPermissionField.Mutable.toString(), true],1239            [TokenPermissionField.TokenOwner.toString(), false],1240            [TokenPermissionField.CollectionAdmin.toString(), true]],1241          ],1242          ['testKey_2', [1243            [TokenPermissionField.Mutable.toString(), false],1244            [TokenPermissionField.TokenOwner.toString(), true],1245            [TokenPermissionField.CollectionAdmin.toString(), false]],1246          ],1247        ]);1248      }));12491250    [1251      {mode: 'nft' as const, requiredPallets: []},1252      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1253    ].map(testCase =>1254      itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1255        const caller = await helper.eth.createAccountWithBalance(donor);1256        const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1257        const {collectionAddress} = await helper.eth.createCollection(1258          caller,1259          {1260            ...CREATE_COLLECTION_DATA_DEFAULTS,1261            name: 'A',1262            description: 'B',1263            tokenPrefix: 'C',1264            collectionMode: testCase.mode,1265            adminList: [receiver],1266            tokenPropertyPermissions: [1267              {1268                key: 'testKey',1269                permissions: [1270                  {code: TokenPermissionField.Mutable, value: true},1271                  {code: TokenPermissionField.CollectionAdmin, value: true}],1272              },1273              {1274                key: 'testKey_1',1275                permissions: [1276                  {code: TokenPermissionField.Mutable, value: true},1277                  {code: TokenPermissionField.CollectionAdmin, value: true}],1278              },1279            ],1280          },1281        ).send();12821283        const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1284        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;1285        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12861287        await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1288        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1289      }));1290  });12911292  describe('Nesting', () => {1293    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1294      const owner = await helper.eth.createAccountWithBalance(donor);1295      const {collectionAddress, collectionId} = await helper.eth.createCollection(1296        owner,1297        {1298          ...CREATE_COLLECTION_DATA_DEFAULTS,1299          name: 'A',1300          description: 'B',1301          tokenPrefix: 'C',1302          collectionMode: 'nft',1303          nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1304        },1305      ).send();13061307      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13081309      // Create a token to be nested to1310      const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1311      const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1312      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13131314      // Create a nested token1315      const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1316      const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1317      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13181319      // Create a token to be nested and nest1320      const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1321      const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13221323      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1324      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13251326      // Unnest token back1327      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1328      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1329    });13301331    itEth('NFT: collectionNesting()', async ({helper}) => {1332      const owner = await helper.eth.createAccountWithBalance(donor);1333      const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1334        owner,1335        new CreateCollectionData('A', 'B', 'C', 'nft'),1336      ).send();13371338      const unnestedContract = helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1339      expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13401341      const {collectionAddress} = await helper.eth.createCollection(1342        owner,1343        {1344          ...CREATE_COLLECTION_DATA_DEFAULTS,1345          name: 'A',1346          description: 'B',1347          tokenPrefix: 'C',1348          collectionMode: 'nft',1349          nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1350        },1351      ).send();13521353      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1354      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1355      await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1356      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1357    });13581359    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1360      const owner = await helper.eth.createAccountWithBalance(donor);13611362      const {collectionId, collectionAddress} = await helper.eth.createCollection(1363        owner,1364        {1365          ...CREATE_COLLECTION_DATA_DEFAULTS,1366          name: 'A',1367          description: 'B',1368          tokenPrefix: 'C',1369          collectionMode: 'nft',1370          nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1371        },1372      ).send();13731374      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13751376      // Create a token to nest into1377      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1378      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1379      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13801381      // Create a token to nest1382      const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1383      const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13841385      // Try to nest1386      await expect(contract.methods1387        .transfer(targetNftTokenAddress, nftTokenId)1388        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1389    });1390  });13911392  describe('Flags', () => {1393    const createCollectionData = {1394      ...CREATE_COLLECTION_DATA_DEFAULTS,1395      name: 'A',1396      description: 'B',1397      tokenPrefix: 'C',1398      collectionMode: 'nft' as TCollectionMode,1399    };14001401    itEth('NFT: use numbers for flags', async ({helper}) => {1402      const owner = await helper.eth.createAccountWithBalance(donor);14031404      {1405        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1406        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1407      }14081409      {1410        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1411        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1412      }1413    });14141415    itEth('NFT: can\'t set foreign flag number', async ({helper}) => {1416      const owner = await helper.eth.createAccountWithBalance(donor);14171418      {1419        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1420      }14211422      {1423        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1424      }1425    });14261427    itEth('NFT: use enum for flags', async ({helper}) => {1428      const owner = await helper.eth.createAccountWithBalance(donor);14291430      {1431        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1432        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1433      }1434    });14351436    itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1437      const owner = await helper.eth.createAccountWithBalance(donor);14381439      {1440        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1441      }14421443      {1444        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1445      }1446    });1447  });1448});
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,