git.delta.rocks / unique-network / refs/commits / 0ce92f01d899

difftreelog

fix tests cleanup

Andy Smith2023-08-29parent: #905f9b3.patch.diff
in: master

10 files changed

modifiedtests/src/eth/collectionLimits.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionLimits.test.ts
+++ b/tests/src/eth/collectionLimits.test.ts
@@ -97,7 +97,7 @@
 
       const owner = await helper.eth.createAccountWithBalance(donor);
       const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'ISNI', testCase.case, 18)).send();
-      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);
+      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);
 
       // Cannot set non-existing limit
       await expect(collectionEvm.methods
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  [],27  [],28  [],29  [false, false, []],30  [],31  [0],32];3334type ElementOf<A> = A extends readonly (infer T)[] ? T : never;35function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {36  if(args.length === 0) {37    yield internalRest as any;38    return;39  }40  for(const value of args[0]) {41    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;42  }43}4445describe('Create collection from EVM', () => {46  let donor: IKeyringPair;47  let nominal: bigint;4849  before(async function() {50    await usingEthPlaygrounds(async (helper, privateKey) => {51      donor = await privateKey({url: import.meta.url});52      nominal = helper.balance.getOneTokenNominal();53    });54  });5556  describe('Fungible collection', () => {57    before(async function() {58      await usingEthPlaygrounds((helper) => {59        requirePalletsOrSkip(this, helper, [Pallets.Fungible]);60        return Promise.resolve();61      });62    });6364    itEth('Collection address exist', async ({helper}) => {65      const owner = await helper.eth.createAccountWithBalance(donor);66      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';67      const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);6869      expect(await collectionHelpers70        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())71        .to.be.false;7273      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();74      expect(await collectionHelpers75        .methods.isCollectionExist(collectionAddress).call())76        .to.be.true;7778      // check collectionOwner:79      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);80      const collectionOwner = await collectionEvm.methods.collectionOwner().call();81      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));82    });8384    itEth('destroyCollection', async ({helper}) => {85      const owner = await helper.eth.createAccountWithBalance(donor);86      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();87      const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);8889      const result = await collectionHelper.methods90        .destroyCollection(collectionAddress)91        .send({from: owner});9293      const events = helper.eth.normalizeEvents(result.events);9495      expect(events).to.be.deep.equal([96        {97          address: collectionHelper.options.address,98          event: 'CollectionDestroyed',99          args: {100            collectionId: collectionAddress,101          },102        },103      ]);104105      expect(await collectionHelper.methods106        .isCollectionExist(collectionAddress)107        .call()).to.be.false;108      expect(await helper.collection.getData(collectionId)).to.be.null;109    });110111    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {112      const owner = await helper.eth.createAccountWithBalance(donor);113      const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);114      {115        const MAX_NAME_LENGTH = 64;116        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);117        const description = 'A';118        const tokenPrefix = 'A';119120        await expect(collectionHelper.methods121          .createCollection([122            emptyAddress,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            emptyAddress,140            collectionName,141            description,142            tokenPrefix,143            CollectionMode.Fungible,144            DECIMALS,145            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,146          ])147          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);148      }149      {150        const MAX_TOKEN_PREFIX_LENGTH = 16;151        const collectionName = 'A';152        const description = 'A';153        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);154        await expect(collectionHelper.methods155          .createCollection([156            emptyAddress,157            collectionName,158            description,159            tokenPrefix,160            CollectionMode.Fungible,161            DECIMALS,162            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,163          ])164          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);165      }166    });167168    itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => {169      const owner = await helper.eth.createAccountWithBalance(donor);170      const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);171      const expects = [0n, 1n, 30n].map(async value => {172        await expect(collectionHelper.methods173          .createCollection([174            emptyAddress,175            'Peasantry',176            'absolutely anything',177            'TWIW',178            CollectionMode.Fungible,179            DECIMALS,180            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,181          ])182          .call({value: Number(value * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');183      });184      await Promise.all(expects);185    });186  });187188  describe('Nonfungible collection', () => {189    before(async function() {190      await usingEthPlaygrounds((helper) => {191        requirePalletsOrSkip(this, helper, [Pallets.NFT]);192        return Promise.resolve();193      });194    });195196    itEth('Create collection', async ({helper}) => {197      const owner = await helper.eth.createAccountWithBalance(donor);198199      const name = 'CollectionEVM';200      const description = 'Some description';201      const prefix = 'token prefix';202203      // todo:playgrounds this might fail when in async environment.204      const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;205      const {collectionId, collectionAddress, events} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'nft')).send();206207      expect(events).to.be.deep.equal([208        {209          address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',210          event: 'CollectionCreated',211          args: {212            owner: owner,213            collectionId: collectionAddress,214          },215        },216      ]);217218      const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;219220      const collection = helper.nft.getCollectionObject(collectionId);221      const data = (await collection.getData())!;222223      expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);224      expect(collectionId).to.be.eq(collectionCountAfter);225      expect(data.name).to.be.eq(name);226      expect(data.description).to.be.eq(description);227      expect(data.raw.tokenPrefix).to.be.eq(prefix);228      expect(data.raw.mode).to.be.eq('NFT');229230      const options = await collection.getOptions();231232      expect(options.tokenPropertyPermissions).to.be.empty;233    });234235    // this test will occasionally fail when in async environment.236    itEth('Check collection address exist', async ({helper}) => {237      const owner = await helper.eth.createAccountWithBalance(donor);238239      const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;240      const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);241      const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);242243      expect(await collectionHelpers.methods244        .isCollectionExist(expectedCollectionAddress)245        .call()).to.be.false;246247      await collectionHelpers.methods248        .createCollection([249          emptyAddress,250          'A',251          'A',252          'A',253          CollectionMode.Nonfungible,254          0,255          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,256        ])257        .send({value: Number(2n * helper.balance.getOneTokenNominal())});258259      expect(await collectionHelpers.methods260        .isCollectionExist(expectedCollectionAddress)261        .call()).to.be.true;262    });263  });264265  describe('Create RFT collection from EVM', () => {266    before(async function() {267      await usingEthPlaygrounds((helper) => {268        requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);269        return Promise.resolve();270      });271    });272273    itEth('Create collection', async ({helper}) => {274      const owner = await helper.eth.createAccountWithBalance(donor);275276      const name = 'CollectionEVM';277      const description = 'Some description';278      const prefix = 'token prefix';279280      const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();281      const data = (await helper.rft.getData(collectionId))!;282      const collection = helper.rft.getCollectionObject(collectionId);283284      expect(data.name).to.be.eq(name);285      expect(data.description).to.be.eq(description);286      expect(data.raw.tokenPrefix).to.be.eq(prefix);287      expect(data.raw.mode).to.be.eq('ReFungible');288289      const options = await collection.getOptions();290291      expect(options.tokenPropertyPermissions).to.be.empty;292    });293294    itEth('Create collection with properties & get description', async ({helper}) => {295      const owner = await helper.eth.createAccountWithBalance(donor);296297      const name = 'CollectionEVM';298      const description = 'Some description';299      const prefix = 'token prefix';300      const baseUri = 'BaseURI';301302      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);303      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');304305      const collection = helper.rft.getCollectionObject(collectionId);306      const data = (await collection.getData())!;307308      expect(data.name).to.be.eq(name);309      expect(data.description).to.be.eq(description);310      expect(data.raw.tokenPrefix).to.be.eq(prefix);311      expect(data.raw.mode).to.be.eq('ReFungible');312313      expect(await contract.methods.description().call()).to.deep.equal(description);314315      const options = await collection.getOptions();316      expect(options.tokenPropertyPermissions).to.be.deep.equal([317        {318          key: 'URI',319          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},320        },321        {322          key: 'URISuffix',323          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},324        },325      ]);326    });327328    itEth('Set sponsorship', async ({helper}) => {329      const owner = await helper.eth.createAccountWithBalance(donor);330      const sponsor = await helper.eth.createAccountWithBalance(donor);331      const ss58Format = helper.chain.getChainProperties().ss58Format;332      const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();333334      const collection = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);335      const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);336      await collection.methods.setCollectionSponsorCross(sponsorCross).send();337338      let data = (await helper.rft.getData(collectionId))!;339      expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));340341      await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');342343      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);344      await sponsorCollection.methods.confirmCollectionSponsorship().send();345346      data = (await helper.rft.getData(collectionId))!;347      expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));348    });349350    itEth('Collection address exist', async ({helper}) => {351      const owner = await helper.eth.createAccountWithBalance(donor);352      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';353      const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);354355      expect(await collectionHelpers356        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())357        .to.be.false;358359      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();360      expect(await collectionHelpers361        .methods.isCollectionExist(collectionAddress).call())362        .to.be.true;363364      // check collectionOwner:365      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);366      const collectionOwner = await collectionEvm.methods.collectionOwner().call();367      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));368    });369370    itEth('destroyCollection', async ({helper}) => {371      const owner = await helper.eth.createAccountWithBalance(donor);372      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();373      const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);374375      await expect(collectionHelper.methods376        .destroyCollection(collectionAddress)377        .send({from: owner})).to.be.fulfilled;378379      expect(await collectionHelper.methods380        .isCollectionExist(collectionAddress)381        .call()).to.be.false;382      expect(await helper.collection.getData(collectionId)).to.be.null;383    });384385    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {386      const owner = await helper.eth.createAccountWithBalance(donor);387      const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);388      {389        const MAX_NAME_LENGTH = 64;390        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);391        const description = 'A';392        const tokenPrefix = 'A';393394        await expect(collectionHelper.methods395          .createCollection([396            emptyAddress,397            collectionName,398            description,399            tokenPrefix,400            CollectionMode.Refungible,401            DECIMALS,402            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,403          ])404          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);405      }406      {407        const MAX_DESCRIPTION_LENGTH = 256;408        const collectionName = 'A';409        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);410        const tokenPrefix = 'A';411        await expect(collectionHelper.methods412          .createCollection([413            emptyAddress,414            collectionName,415            description,416            tokenPrefix,417            CollectionMode.Refungible,418            DECIMALS,419            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,420          ])421          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);422      }423      {424        const MAX_TOKEN_PREFIX_LENGTH = 16;425        const collectionName = 'A';426        const description = 'A';427        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);428        await expect(collectionHelper.methods429          .createCollection([430            emptyAddress,431            collectionName,432            description,433            tokenPrefix,434            CollectionMode.Refungible,435            DECIMALS,436            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,437          ])438          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);439      }440    });441442    itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {443      const owner = await helper.eth.createAccountWithBalance(donor);444      const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);445      await expect(collectionHelper.methods446        .createCollection([447          emptyAddress,448          'Peasantry',449          'absolutely anything',450          'TWIW',451          CollectionMode.Refungible,452          0,453          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,454        ])455        .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');456    });457  });458459  describe('Sponsoring', () => {460    itEth('Сan remove collection sponsor', async ({helper}) => {461      const owner = await helper.eth.createAccountWithBalance(donor);462      const sponsor = await helper.eth.createAccountWithBalance(donor);463      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);464465      const {collectionAddress} = await helper.eth.createCollection(466        owner,467        {468          ...CREATE_COLLECTION_DATA_DEFAULTS,469          name: 'Sponsor collection',470          description: '1',471          tokenPrefix: '1',472          collectionMode: 'nft',473          pendingSponsor: sponsorCross,474        },475      ).send();476      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);477478      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;479480      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});481      let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});482      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));483      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;484485      await collectionEvm.methods.removeCollectionSponsor().send({from: owner});486487      sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});488      expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');489    });490491    itEth('Can sponsor from evm address via access list', async ({helper}) => {492      const owner = await helper.eth.createAccountWithBalance(donor);493      const sponsorEth = await helper.eth.createAccountWithBalance(donor);494      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);495496      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(497        owner,498        {499          ...CREATE_COLLECTION_DATA_DEFAULTS,500          name: 'Sponsor collection',501          description: '1',502          tokenPrefix: '1',503          collectionMode: 'nft',504          pendingSponsor: sponsorCross,505          limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],506          tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],507        },508        '',509      );510511      const collectionSub = helper.nft.getCollectionObject(collectionId);512      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);513514      let sponsorship = (await collectionSub.getData())!.raw.sponsorship;515      expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));516      // Account cannot confirm sponsorship if it is not set as a sponsor517      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');518519      // Sponsor can confirm sponsorship:520      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});521      sponsorship = (await collectionSub.getData())!.raw.sponsorship;522      expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));523524      // Create user with no balance:525      const user = helper.ethCrossAccount.createAccount();526      const nextTokenId = await collectionEvm.methods.nextTokenId().call();527      expect(nextTokenId).to.be.equal('1');528529      // Set collection permissions:530      const oldPermissions = (await collectionSub.getData())!.raw.permissions;531      expect(oldPermissions.mintMode).to.be.false;532      expect(oldPermissions.access).to.be.equal('Normal');533534      await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});535      await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});536      await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});537538      const newPermissions = (await collectionSub.getData())!.raw.permissions;539      expect(newPermissions.mintMode).to.be.true;540      expect(newPermissions.access).to.be.equal('AllowList');541542      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));543      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));544      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));545546      // User can mint token without balance:547      {548        const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});549        const event = helper.eth.normalizeEvents(result.events)550          .find(event => event.event === 'Transfer');551552        expect(event).to.be.deep.equal({553          address: collectionAddress,554          event: 'Transfer',555          args: {556            from: '0x0000000000000000000000000000000000000000',557            to: user.eth,558            tokenId: '1',559          },560        });561562        // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});563564        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));565        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));566        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));567568        expect(await collectionEvm.methods.properties(nextTokenId, []).call())569          .to.be.like([570            [571              'key',572              '0x' + Buffer.from('Value').toString('hex'),573            ],574          ]);575        expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);576        expect(userBalanceAfter).to.be.eq(userBalanceBefore);577        expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;578      }579    });580581    itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {582      const owner = await helper.eth.createAccountWithBalance(donor);583      const sponsor = await helper.eth.createAccountWithBalance(donor);584      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);585      const user = helper.eth.createAccount();586      const userCross = helper.ethCrossAccount.fromAddress(user);587588      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(589        owner,590        {591          ...CREATE_COLLECTION_DATA_DEFAULTS,592          name: 'Sponsor collection',593          description: '1',594          tokenPrefix: '1',595          collectionMode: 'nft',596          pendingSponsor: sponsorCross,597          adminList: [userCross],598        },599        '',600      );601602      const collectionSub = helper.nft.getCollectionObject(collectionId);603      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);604      // Set collection sponsor:605      let collectionData = (await collectionSub.getData())!;606      expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));607      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');608609      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});610      collectionData = (await collectionSub.getData())!;611      expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));612613      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));614      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));615616      const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});617      const tokenId = mintingResult.events.Transfer.returnValues.tokenId;618619      const event = helper.eth.normalizeEvents(mintingResult.events)620        .find(event => event.event === 'Transfer');621      const address = helper.ethAddress.fromCollectionId(collectionId);622623      expect(event).to.be.deep.equal({624        address,625        event: 'Transfer',626        args: {627          from: '0x0000000000000000000000000000000000000000',628          to: user,629          tokenId: '1',630        },631      });632      expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');633634      const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));635      expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);636      const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));637      expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;638    });639640    itEth('Can reassign collection sponsor', async ({helper}) => {641      const owner = await helper.eth.createAccountWithBalance(donor);642      const sponsorEth = await helper.eth.createAccountWithBalance(donor);643      const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);644      const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);645      const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);646647      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(648        owner,649        {650          ...CREATE_COLLECTION_DATA_DEFAULTS,651          name: 'Sponsor collection',652          description: '1',653          tokenPrefix: '1',654          collectionMode: 'nft',655          pendingSponsor: sponsorCrossEth,656        },657        '',658      );659      const collectionSub = helper.nft.getCollectionObject(collectionId);660      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);661662      // Set and confirm sponsor:663      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});664665      // Can reassign sponsor:666      await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});667      const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;668      expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});669    });670671    [672      'transfer',673      'transferCross',674      'transferFrom',675      'transferFromCross',676    ].map(testCase =>677      itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {678        const owner = await helper.eth.createAccountWithBalance(donor);679        const sponsor = await helper.eth.createAccountWithBalance(donor);680        const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);681        const user = await helper.eth.createAccountWithBalance(donor);682        const userCross = helper.ethCrossAccount.fromAddress(user);683684        const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(685          owner,686          {687            ...CREATE_COLLECTION_DATA_DEFAULTS,688            name: 'Sponsor collection',689            description: '1',690            tokenPrefix: '1',691            collectionMode: 'rft',692            pendingSponsor: sponsorCross,693            adminList: [userCross],694          },695          '',696        );697        const receiver = await helper.eth.createAccountWithBalance(donor);698        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);699700        await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});701702        const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});703        const tokenId = result.events.Transfer.returnValues.tokenId;704705        const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));706        const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));707        const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));708709        switch (testCase) {710          case 'transfer':711            await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});712            break;713          case 'transferCross':714            await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});715            break;716          case 'transferFrom':717            await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});718            break;719          case 'transferFromCross':720            await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});721            break;722        }723724        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));725        expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);726        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));727        expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;728        const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));729        expect(userBalanceAfter).to.be.eq(userBalanceBefore);730      }));731  });732733  describe('Collection admins', () => {734    let donor: IKeyringPair;735736    before(async function() {737      await usingEthPlaygrounds(async (_helper, privateKey) => {738        donor = await privateKey({url: import.meta.url});739      });740    });741742    [743      {mode: 'nft' as const, requiredPallets: []},744      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},745      {mode: 'ft' as const, requiredPallets: []},746    ].map(testCase => {747      itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {748        // arrange749        const owner = await helper.eth.createAccountWithBalance(donor);750        const adminSub = await privateKey('//admin2');751        const adminEth = helper.eth.createAccount().toLowerCase();752753        const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);754        const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);755756        const {collectionAddress, collectionId} = await helper.eth.createCollection(757          owner,758          {759            ...CREATE_COLLECTION_DATA_DEFAULTS,760            name: 'Mint collection',761            description: 'a',762            tokenPrefix: 'b',763            collectionMode: testCase.mode,764            adminList: [adminCrossSub, adminCrossEth],765          },766        ).send();767        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);768769        // 1. Expect api.rpc.unique.adminlist returns admins:770        const adminListRpc = await helper.collection.getAdmins(collectionId);771        expect(adminListRpc).to.has.length(2);772        expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);773774        // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist775        let adminListEth = await collectionEvm.methods.collectionAdmins().call();776        adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));777        expect(adminListRpc).to.be.like(adminListEth);778779        // 3. check isOwnerOrAdminCross returns true:780        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;781        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;782      });783    });784785    itEth('cross account admin can mint', async ({helper}) => {786      // arrange: create collection and accounts787      const owner = await helper.eth.createAccountWithBalance(donor);788      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();789      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);790      const [adminSub] = await helper.arrange.createAccounts([100n], donor);791      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);792      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(793        owner,794        {795          ...CREATE_COLLECTION_DATA_DEFAULTS,796          name: 'Mint collection',797          description: 'a',798          tokenPrefix: 'b',799          collectionMode: 'nft',800          adminList: [adminCrossSub, adminCrossEth],801        },802        'uri',803      );804      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);805806      // admin (sub and eth) can mint token:807      await collectionEvm.methods.mint(owner).send({from: adminEth});808      await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});809810      expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);811    });812813    itEth('cannot add invalid cross account admin', async ({helper}) => {814      const owner = await helper.eth.createAccountWithBalance(donor);815      const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);816817      const adminCross = {818        eth: helper.address.substrateToEth(admin.address),819        sub: admin.addressRaw,820      };821822      await expect(helper.eth.createCollection(823        owner,824        {825          ...CREATE_COLLECTION_DATA_DEFAULTS,826          name: 'A',827          description: 'B',828          tokenPrefix: 'C',829          collectionMode: 'nft',830          adminList: [adminCross],831        },832      ).call()).to.be.rejected;833    });834835    itEth('Remove [cross] admin by owner', async ({helper}) => {836      const owner = await helper.eth.createAccountWithBalance(donor);837838      const [adminSub] = await helper.arrange.createAccounts([10n], donor);839      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();840      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);841      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);842843      const {collectionAddress, collectionId} = await helper.eth.createCollection(844        owner,845        {846          ...CREATE_COLLECTION_DATA_DEFAULTS,847          name: 'A',848          description: 'B',849          tokenPrefix: 'C',850          collectionMode: 'nft',851          adminList: [adminCrossSub, adminCrossEth],852        },853      ).send();854855      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);856857      {858        const adminList = await helper.collection.getAdmins(collectionId);859        expect(adminList).to.deep.include({Substrate: adminSub.address});860        expect(adminList).to.deep.include({Ethereum: adminEth});861      }862863      await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();864      await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();865      const adminList = await helper.collection.getAdmins(collectionId);866      expect(adminList.length).to.be.eq(0);867868      // Non admin cannot mint:869      await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);870      await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;871    });872  });873874  describe('Collection limits', () => {875    describe('Can set collection limits', () => {876      [877        {case: 'nft' as const},878        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},879        {case: 'ft' as const},880      ].map(testCase =>881        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {882          const owner = await helper.eth.createAccountWithBalance(donor);883884          const limits = {885            accountTokenOwnershipLimit: 1000n,886            sponsoredDataSize: 1024n,887            sponsoredDataRateLimit: 30n,888            tokenLimit: 1000000n,889            sponsorTransferTimeout: 6n,890            sponsorApproveTimeout: 6n,891            ownerCanTransfer: 1n,892            ownerCanDestroy: 0n,893            transfersEnabled: 0n,894          };895896          const {collectionId, collectionAddress} = await helper.eth.createCollection(897            owner,898            {899              ...CREATE_COLLECTION_DATA_DEFAULTS,900              name: 'Limits',901              description: 'absolutely anything',902              tokenPrefix: 'FLO',903              collectionMode: testCase.case,904              limits: [905                {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},906                {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},907                {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},908                {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},909                {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},910                {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},911                {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},912                {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},913                {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},914              ],915            },916          ).send();917918          const expectedLimits = {919            accountTokenOwnershipLimit: 1000,920            sponsoredDataSize: 1024,921            sponsoredDataRateLimit: {blocks: 30},922            tokenLimit: 1000000,923            sponsorTransferTimeout: 6,924            sponsorApproveTimeout: 6,925            ownerCanTransfer: true,926            ownerCanDestroy: false,927            transfersEnabled: false,928          };929930          const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);931932          // Check limits from sub:933          const data = (await helper.rft.getData(collectionId))!;934          expect(data.raw.limits).to.deep.eq(expectedLimits);935          expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);936          // Check limits from eth:937          const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});938          expect(limitsEvm).to.have.length(9);939          expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);940          expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);941          expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);942          expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);943          expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);944          expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);945          expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);946          expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);947          expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);948        }));949    });950951    describe('(!negative test!) Cannot set invalid collection limits', () => {952      [953        {case: 'nft' as const},954        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},955        {case: 'ft' as const},956      ].map(testCase =>957        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {958          const invalidLimits = {959            accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),960            transfersEnabled: 3,961          };962963          const createCollectionData = {964            ...CREATE_COLLECTION_DATA_DEFAULTS,965            name: 'Limits',966            description: 'absolutely anything',967            tokenPrefix: 'ISNI',968            collectionMode: testCase.case,969          };970971          const owner = await helper.eth.createAccountWithBalance(donor);972          await expect(helper.eth.createCollection(973            owner,974            {975              ...createCollectionData,976              limits: [{field: 9 as CollectionLimitField, value: 1n}],977            },978          ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');979980          await expect(helper.eth.createCollection(981            owner,982            {983              ...createCollectionData,984              limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],985            },986          ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);987988          await expect(helper.eth.createCollection(989            owner,990            {991              ...createCollectionData,992              limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],993            },994          ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);995996          await expect(helper.eth.createCollection(997            owner,998            {999              ...createCollectionData,1000              limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],1001            },1002          ).call()).to.be.rejectedWith('value out-of-bounds');1003        }));1004    });1005  });10061007  describe('Collection properties', () => {10081009    [1010      {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'}]},1011      {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'}]},1012      {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'}]},1013    ].map(testCase =>1014      itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1015        const caller = await helper.eth.createAccountWithBalance(donor);1016        const callerCross = helper.ethCrossAccount.fromAddress(caller);1017        const owner = await helper.eth.createAccountWithBalance(donor);1018        const {collectionId, collectionAddress} = await helper.eth.createCollection(1019          owner,1020          {1021            ...CREATE_COLLECTION_DATA_DEFAULTS,1022            name: 'name',1023            description: 'test',1024            tokenPrefix: 'test',1025            collectionMode: testCase.mode,1026            adminList: [callerCross],1027            properties: testCase.methodParams,1028          },1029        ).send();10301031        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10321033        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1034        expect(raw.properties).to.deep.equal(testCase.expectedProps);10351036        // collectionProperties returns properties:1037        expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1038      }));10391040    [1041      {mode: 'nft' as const},1042      {mode: 'rft' as const},1043      {mode: 'ft' as const},1044    ].map(testCase =>1045      itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1046        const caller = await helper.eth.createAccountWithBalance(donor);1047        const callerCross = helper.ethCrossAccount.fromAddress(caller);1048        const owner = await helper.eth.createAccountWithBalance(donor);1049        const {collectionId, collectionAddress} = await helper.eth.createCollection(1050          owner,1051          {1052            ...CREATE_COLLECTION_DATA_DEFAULTS,1053            name: 'name',1054            description: 'test',1055            tokenPrefix: 'test',1056            collectionMode: testCase.mode,1057            adminList: [callerCross],1058            properties:[1059              {key: 'testKey1', value: Buffer.from('testValue1')},1060              {key: 'testKey2', value: Buffer.from('testValue2')},1061              {key: 'testKey3', value: Buffer.from('testValue3')}],1062          },1063        ).send();10641065        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10661067        await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10681069        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10701071        expect(raw.properties.length).to.equal(1);1072        expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1073      }));10741075    itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1076      const caller = await helper.eth.createAccountWithBalance(donor);1077      const callerCross = helper.ethCrossAccount.fromAddress(caller);1078      const owner = await helper.eth.createAccountWithBalance(donor);1079      const createCollectionData = {1080        ...CREATE_COLLECTION_DATA_DEFAULTS,1081        name: 'name',1082        description: 'test',1083        tokenPrefix: 'test',1084        collectionMode: 'nft' as TCollectionMode,1085        adminList: [callerCross],1086      };1087      await expect(helper.eth.createCollection(1088        owner,1089        {1090          ...createCollectionData,1091          properties: [{key: '', value: Buffer.from('val1')}],1092        },1093      ).call()).to.be.rejected;10941095      await expect(helper.eth.createCollection(1096        owner,1097        {1098          ...createCollectionData,1099          properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1100        },1101      ).call()).to.be.rejected;11021103      await expect(helper.eth.createCollection(1104        owner,1105        {1106          ...createCollectionData,1107          properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1108        },1109      ).call()).to.be.rejected;1110    });11111112    itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1113      const caller = await helper.eth.createAccountWithBalance(donor);1114      const owner = await helper.eth.createAccountWithBalance(donor);1115      const {collectionAddress} = await helper.eth.createCollection(1116        owner,1117        {1118          ...CREATE_COLLECTION_DATA_DEFAULTS,1119          name: 'name',1120          description: 'test',1121          tokenPrefix: 'test',1122          collectionMode: 'nft',1123          properties:[1124            {key: 'testKey1', value: Buffer.from('testValue1')},1125            {key: 'testKey2', value: Buffer.from('testValue2')}],1126        },1127      ).send();11281129      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11301131      await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1132    });1133  });11341135  describe('Token property permissions', () => {1136    [1137      {mode: 'nft' as const, requiredPallets: []},1138      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1139    ].map(testCase =>1140      itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1141        const owner = await helper.eth.createAccountWithBalance(donor);1142        const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1143        for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1144          const {collectionId, collectionAddress} = await helper.eth.createCollection(1145            owner,1146            {1147              ...CREATE_COLLECTION_DATA_DEFAULTS,1148              name: 'A',1149              description: 'B',1150              tokenPrefix: 'C',1151              collectionMode: testCase.mode,1152              adminList: [caller],1153              tokenPropertyPermissions: [1154                {1155                  key: 'testKey',1156                  permissions: [1157                    {code: TokenPermissionField.Mutable, value: mutable},1158                    {code: TokenPermissionField.TokenOwner, value: tokenOwner},1159                    {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1160                  ],1161                },1162              ],1163            },1164          ).send();1165          const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11661167          expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1168            key: 'testKey',1169            permission: {mutable, collectionAdmin, tokenOwner},1170          }]);11711172          expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1173            ['testKey', [1174              [TokenPermissionField.Mutable.toString(), mutable],1175              [TokenPermissionField.TokenOwner.toString(), tokenOwner],1176              [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1177            ],1178          ]);1179        }1180      }));11811182    [1183      {mode: 'nft' as const, requiredPallets: []},1184      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1185    ].map(testCase =>1186      itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1187        const owner = await helper.eth.createAccountWithBalance(donor);1188        const {collectionId, collectionAddress} = await helper.eth.createCollection(1189          owner,1190          {1191            ...CREATE_COLLECTION_DATA_DEFAULTS,1192            name: 'A',1193            description: 'B',1194            tokenPrefix: 'C',1195            collectionMode: testCase.mode,1196            tokenPropertyPermissions: [1197              {1198                key: 'testKey_0',1199                permissions: [1200                  {code: TokenPermissionField.Mutable, value: true},1201                  {code: TokenPermissionField.TokenOwner, value: true},1202                  {code: TokenPermissionField.CollectionAdmin, value: true}],1203              },1204              {1205                key: 'testKey_1',1206                permissions: [1207                  {code: TokenPermissionField.Mutable, value: true},1208                  {code: TokenPermissionField.TokenOwner, value: false},1209                  {code: TokenPermissionField.CollectionAdmin, value: true}],1210              },1211              {1212                key: 'testKey_2',1213                permissions: [1214                  {code: TokenPermissionField.Mutable, value: false},1215                  {code: TokenPermissionField.TokenOwner, value: true},1216                  {code: TokenPermissionField.CollectionAdmin, value: false}],1217              },1218            ],1219          },1220        ).send();1221        const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12221223        expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1224          {1225            key: 'testKey_0',1226            permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1227          },1228          {1229            key: 'testKey_1',1230            permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1231          },1232          {1233            key: 'testKey_2',1234            permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1235          },1236        ]);12371238        expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1239          ['testKey_0', [1240            [TokenPermissionField.Mutable.toString(), true],1241            [TokenPermissionField.TokenOwner.toString(), true],1242            [TokenPermissionField.CollectionAdmin.toString(), true]],1243          ],1244          ['testKey_1', [1245            [TokenPermissionField.Mutable.toString(), true],1246            [TokenPermissionField.TokenOwner.toString(), false],1247            [TokenPermissionField.CollectionAdmin.toString(), true]],1248          ],1249          ['testKey_2', [1250            [TokenPermissionField.Mutable.toString(), false],1251            [TokenPermissionField.TokenOwner.toString(), true],1252            [TokenPermissionField.CollectionAdmin.toString(), false]],1253          ],1254        ]);1255      }));12561257    [1258      {mode: 'nft' as const, requiredPallets: []},1259      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1260    ].map(testCase =>1261      itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1262        const caller = await helper.eth.createAccountWithBalance(donor);1263        const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1264        const {collectionAddress} = await helper.eth.createCollection(1265          caller,1266          {1267            ...CREATE_COLLECTION_DATA_DEFAULTS,1268            name: 'A',1269            description: 'B',1270            tokenPrefix: 'C',1271            collectionMode: testCase.mode,1272            adminList: [receiver],1273            tokenPropertyPermissions: [1274              {1275                key: 'testKey',1276                permissions: [1277                  {code: TokenPermissionField.Mutable, value: true},1278                  {code: TokenPermissionField.CollectionAdmin, value: true}],1279              },1280              {1281                key: 'testKey_1',1282                permissions: [1283                  {code: TokenPermissionField.Mutable, value: true},1284                  {code: TokenPermissionField.CollectionAdmin, value: true}],1285              },1286            ],1287          },1288        ).send();12891290        const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1291        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;1292        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12931294        await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1295        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1296      }));1297  });12981299  describe('Nesting', () => {1300    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1301      const owner = await helper.eth.createAccountWithBalance(donor);1302      const {collectionAddress, collectionId} = await helper.eth.createCollection(1303        owner,1304        {1305          ...CREATE_COLLECTION_DATA_DEFAULTS,1306          name: 'A',1307          description: 'B',1308          tokenPrefix: 'C',1309          collectionMode: 'nft',1310          nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1311        },1312      ).send();13131314      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13151316      // Create a token to be nested to1317      const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1318      const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1319      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13201321      // Create a nested token1322      const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1323      const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1324      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13251326      // Create a token to be nested and nest1327      const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1328      const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13291330      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1331      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13321333      // Unnest token back1334      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1335      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1336    });13371338    itEth('NFT: collectionNesting()', async ({helper}) => {1339      const owner = await helper.eth.createAccountWithBalance(donor);1340      const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1341        owner,1342        new CreateCollectionData('A', 'B', 'C', 'nft'),1343      ).send();13441345      const unnestedContract = await helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1346      expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13471348      const {collectionAddress} = await helper.eth.createCollection(1349        owner,1350        {1351          ...CREATE_COLLECTION_DATA_DEFAULTS,1352          name: 'A',1353          description: 'B',1354          tokenPrefix: 'C',1355          collectionMode: 'nft',1356          nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1357        },1358      ).send();13591360      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1361      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1362      await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1363      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1364    });13651366    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1367      const owner = await helper.eth.createAccountWithBalance(donor);13681369      const {collectionId, collectionAddress} = await helper.eth.createCollection(1370        owner,1371        {1372          ...CREATE_COLLECTION_DATA_DEFAULTS,1373          name: 'A',1374          description: 'B',1375          tokenPrefix: 'C',1376          collectionMode: 'nft',1377          nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1378        },1379      ).send();13801381      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13821383      // Create a token to nest into1384      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1385      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1386      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13871388      // Create a token to nest1389      const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1390      const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13911392      // Try to nest1393      await expect(contract.methods1394        .transfer(targetNftTokenAddress, nftTokenId)1395        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1396    });1397  });13981399  describe('Flags', () => {1400    const createCollectionData = {1401      ...CREATE_COLLECTION_DATA_DEFAULTS,1402      name: 'A',1403      description: 'B',1404      tokenPrefix: 'C',1405      collectionMode: 'nft' as TCollectionMode,1406    };14071408    itEth('NFT: use numbers for flags', async ({helper}) => {1409      const owner = await helper.eth.createAccountWithBalance(donor);14101411      {1412        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1413        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1414      }14151416      {1417        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1418        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1419      }1420    });14211422    itEth('NFT: foreign flag number is ignored', async ({helper}) => {1423      const owner = await helper.eth.createAccountWithBalance(donor);14241425      {1426        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).send();1427        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1428      }14291430      {1431        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).send();1432        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1433      }1434    });14351436    itEth('NFT: use enum for flags', async ({helper}) => {1437      const owner = await helper.eth.createAccountWithBalance(donor);14381439      {1440        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1441        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1442      }1443    });14441445    itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1446      const owner = await helper.eth.createAccountWithBalance(donor);14471448      {1449        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).send();1450        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1451      }14521453      {1454        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).send();1455        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1456      }1457    });1458  });1459});
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      expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);221      expect(collectionId).to.be.eq(collectionCountAfter);222      expect(data.name).to.be.eq(name);223      expect(data.description).to.be.eq(description);224      expect(data.raw.tokenPrefix).to.be.eq(prefix);225      expect(data.raw.mode).to.be.eq('NFT');226227      const options = await collection.getOptions();228229      expect(options.tokenPropertyPermissions).to.be.empty;230    });231232    // this test will occasionally fail when in async environment.233    itEth('Check collection address exist', async ({helper}) => {234      const owner = await helper.eth.createAccountWithBalance(donor);235236      const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;237      const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);238      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);239240      expect(await collectionHelpers.methods241        .isCollectionExist(expectedCollectionAddress)242        .call()).to.be.false;243244      await collectionHelpers.methods245        .createCollection([246          'A',247          'A',248          'A',249          CollectionMode.Nonfungible,250          0,251          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,252        ])253        .send({value: Number(2n * helper.balance.getOneTokenNominal())});254255      expect(await collectionHelpers.methods256        .isCollectionExist(expectedCollectionAddress)257        .call()).to.be.true;258    });259  });260261  describe('Create RFT collection from EVM', () => {262    before(async function() {263      await usingEthPlaygrounds((helper) => {264        requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);265        return Promise.resolve();266      });267    });268269    itEth('Create collection', async ({helper}) => {270      const owner = await helper.eth.createAccountWithBalance(donor);271272      const name = 'CollectionEVM';273      const description = 'Some description';274      const prefix = 'token prefix';275276      const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();277      const data = (await helper.rft.getData(collectionId))!;278      const collection = helper.rft.getCollectionObject(collectionId);279280      expect(data.name).to.be.eq(name);281      expect(data.description).to.be.eq(description);282      expect(data.raw.tokenPrefix).to.be.eq(prefix);283      expect(data.raw.mode).to.be.eq('ReFungible');284285      const options = await collection.getOptions();286287      expect(options.tokenPropertyPermissions).to.be.empty;288    });289290    itEth('Create collection with properties & get description', async ({helper}) => {291      const owner = await helper.eth.createAccountWithBalance(donor);292293      const name = 'CollectionEVM';294      const description = 'Some description';295      const prefix = 'token prefix';296      const baseUri = 'BaseURI';297298      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);299      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');300301      const collection = helper.rft.getCollectionObject(collectionId);302      const data = (await collection.getData())!;303304      expect(data.name).to.be.eq(name);305      expect(data.description).to.be.eq(description);306      expect(data.raw.tokenPrefix).to.be.eq(prefix);307      expect(data.raw.mode).to.be.eq('ReFungible');308309      expect(await contract.methods.description().call()).to.deep.equal(description);310311      const options = await collection.getOptions();312      expect(options.tokenPropertyPermissions).to.be.deep.equal([313        {314          key: 'URI',315          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},316        },317        {318          key: 'URISuffix',319          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},320        },321      ]);322    });323324    itEth('Set sponsorship', async ({helper}) => {325      const owner = await helper.eth.createAccountWithBalance(donor);326      const sponsor = await helper.eth.createAccountWithBalance(donor);327      const ss58Format = helper.chain.getChainProperties().ss58Format;328      const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();329330      const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);331      const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);332      await collection.methods.setCollectionSponsorCross(sponsorCross).send();333334      let data = (await helper.rft.getData(collectionId))!;335      expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));336337      await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');338339      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);340      await sponsorCollection.methods.confirmCollectionSponsorship().send();341342      data = (await helper.rft.getData(collectionId))!;343      expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));344    });345346    itEth('Collection address exist', async ({helper}) => {347      const owner = await helper.eth.createAccountWithBalance(donor);348      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';349      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);350351      expect(await collectionHelpers352        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())353        .to.be.false;354355      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();356      expect(await collectionHelpers357        .methods.isCollectionExist(collectionAddress).call())358        .to.be.true;359360      // check collectionOwner:361      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);362      const collectionOwner = await collectionEvm.methods.collectionOwner().call();363      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));364    });365366    itEth('destroyCollection', async ({helper}) => {367      const owner = await helper.eth.createAccountWithBalance(donor);368      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();369      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);370371      await expect(collectionHelper.methods372        .destroyCollection(collectionAddress)373        .send({from: owner})).to.be.fulfilled;374375      expect(await collectionHelper.methods376        .isCollectionExist(collectionAddress)377        .call()).to.be.false;378      expect(await helper.collection.getData(collectionId)).to.be.null;379    });380381    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {382      const owner = await helper.eth.createAccountWithBalance(donor);383      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);384      {385        const MAX_NAME_LENGTH = 64;386        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);387        const description = 'A';388        const tokenPrefix = 'A';389390        await expect(collectionHelper.methods391          .createCollection([392            collectionName,393            description,394            tokenPrefix,395            CollectionMode.Refungible,396            DECIMALS,397            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,398          ])399          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);400      }401      {402        const MAX_DESCRIPTION_LENGTH = 256;403        const collectionName = 'A';404        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);405        const tokenPrefix = 'A';406        await expect(collectionHelper.methods407          .createCollection([408            collectionName,409            description,410            tokenPrefix,411            CollectionMode.Refungible,412            DECIMALS,413            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,414          ])415          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);416      }417      {418        const MAX_TOKEN_PREFIX_LENGTH = 16;419        const collectionName = 'A';420        const description = 'A';421        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);422        await expect(collectionHelper.methods423          .createCollection([424            collectionName,425            description,426            tokenPrefix,427            CollectionMode.Refungible,428            DECIMALS,429            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,430          ])431          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);432      }433    });434435    itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {436      const owner = await helper.eth.createAccountWithBalance(donor);437      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);438      await expect(collectionHelper.methods439        .createCollection([440          'Peasantry',441          'absolutely anything',442          'TWIW',443          CollectionMode.Refungible,444          0,445          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,446        ])447        .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');448    });449  });450451  describe('Sponsoring', () => {452    itEth('Сan remove collection sponsor', async ({helper}) => {453      const owner = await helper.eth.createAccountWithBalance(donor);454      const sponsor = await helper.eth.createAccountWithBalance(donor);455      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);456457      const {collectionAddress} = await helper.eth.createCollection(458        owner,459        {460          ...CREATE_COLLECTION_DATA_DEFAULTS,461          name: 'Sponsor collection',462          description: '1',463          tokenPrefix: '1',464          collectionMode: 'nft',465          pendingSponsor: sponsorCross,466        },467      ).send();468      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);469470      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;471472      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});473      let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});474      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));475      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;476477      await collectionEvm.methods.removeCollectionSponsor().send({from: owner});478479      sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});480      expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');481    });482483    itEth('Can sponsor from evm address via access list', async ({helper}) => {484      const owner = await helper.eth.createAccountWithBalance(donor);485      const sponsorEth = await helper.eth.createAccountWithBalance(donor);486      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);487488      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(489        owner,490        {491          ...CREATE_COLLECTION_DATA_DEFAULTS,492          name: 'Sponsor collection',493          description: '1',494          tokenPrefix: '1',495          collectionMode: 'nft',496          pendingSponsor: sponsorCross,497          limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],498          tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],499        },500        '',501      );502503      const collectionSub = helper.nft.getCollectionObject(collectionId);504      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);505506      let sponsorship = (await collectionSub.getData())!.raw.sponsorship;507      expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));508      // Account cannot confirm sponsorship if it is not set as a sponsor509      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');510511      // Sponsor can confirm sponsorship:512      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});513      sponsorship = (await collectionSub.getData())!.raw.sponsorship;514      expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));515516      // Create user with no balance:517      const user = helper.ethCrossAccount.createAccount();518      const nextTokenId = await collectionEvm.methods.nextTokenId().call();519      expect(nextTokenId).to.be.equal('1');520521      // Set collection permissions:522      const oldPermissions = (await collectionSub.getData())!.raw.permissions;523      expect(oldPermissions.mintMode).to.be.false;524      expect(oldPermissions.access).to.be.equal('Normal');525526      await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});527      await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});528      await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});529530      const newPermissions = (await collectionSub.getData())!.raw.permissions;531      expect(newPermissions.mintMode).to.be.true;532      expect(newPermissions.access).to.be.equal('AllowList');533534      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));535      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));536      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));537538      // User can mint token without balance:539      {540        const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});541        const event = helper.eth.normalizeEvents(result.events)542          .find(event => event.event === 'Transfer');543544        expect(event).to.be.deep.equal({545          address: collectionAddress,546          event: 'Transfer',547          args: {548            from: '0x0000000000000000000000000000000000000000',549            to: user.eth,550            tokenId: '1',551          },552        });553554        // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});555556        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));557        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));558        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));559560        expect(await collectionEvm.methods.properties(nextTokenId, []).call())561          .to.be.like([562            [563              'key',564              '0x' + Buffer.from('Value').toString('hex'),565            ],566          ]);567        expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);568        expect(userBalanceAfter).to.be.eq(userBalanceBefore);569        expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;570      }571    });572573    itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {574      const owner = await helper.eth.createAccountWithBalance(donor);575      const sponsor = await helper.eth.createAccountWithBalance(donor);576      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);577      const user = helper.eth.createAccount();578      const userCross = helper.ethCrossAccount.fromAddress(user);579580      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(581        owner,582        {583          ...CREATE_COLLECTION_DATA_DEFAULTS,584          name: 'Sponsor collection',585          description: '1',586          tokenPrefix: '1',587          collectionMode: 'nft',588          pendingSponsor: sponsorCross,589          adminList: [userCross],590        },591        '',592      );593594      const collectionSub = helper.nft.getCollectionObject(collectionId);595      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);596      // Set collection sponsor:597      let collectionData = (await collectionSub.getData())!;598      expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));599      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');600601      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});602      collectionData = (await collectionSub.getData())!;603      expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));604605      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));606      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));607608      const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});609      const tokenId = mintingResult.events.Transfer.returnValues.tokenId;610611      const event = helper.eth.normalizeEvents(mintingResult.events)612        .find(event => event.event === 'Transfer');613      const address = helper.ethAddress.fromCollectionId(collectionId);614615      expect(event).to.be.deep.equal({616        address,617        event: 'Transfer',618        args: {619          from: '0x0000000000000000000000000000000000000000',620          to: user,621          tokenId: '1',622        },623      });624      expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');625626      const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));627      expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);628      const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));629      expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;630    });631632    itEth('Can reassign collection sponsor', async ({helper}) => {633      const owner = await helper.eth.createAccountWithBalance(donor);634      const sponsorEth = await helper.eth.createAccountWithBalance(donor);635      const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);636      const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);637      const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);638639      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(640        owner,641        {642          ...CREATE_COLLECTION_DATA_DEFAULTS,643          name: 'Sponsor collection',644          description: '1',645          tokenPrefix: '1',646          collectionMode: 'nft',647          pendingSponsor: sponsorCrossEth,648        },649        '',650      );651      const collectionSub = helper.nft.getCollectionObject(collectionId);652      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);653654      // Set and confirm sponsor:655      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});656657      // Can reassign sponsor:658      await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});659      const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;660      expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});661    });662663    [664      'transfer',665      'transferCross',666      'transferFrom',667      'transferFromCross',668    ].map(testCase =>669      itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {670        const owner = await helper.eth.createAccountWithBalance(donor);671        const sponsor = await helper.eth.createAccountWithBalance(donor);672        const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);673        const user = await helper.eth.createAccountWithBalance(donor);674        const userCross = helper.ethCrossAccount.fromAddress(user);675676        const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(677          owner,678          {679            ...CREATE_COLLECTION_DATA_DEFAULTS,680            name: 'Sponsor collection',681            description: '1',682            tokenPrefix: '1',683            collectionMode: 'rft',684            pendingSponsor: sponsorCross,685            adminList: [userCross],686          },687          '',688        );689        const receiver = await helper.eth.createAccountWithBalance(donor);690        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);691692        await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});693694        const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});695        const tokenId = result.events.Transfer.returnValues.tokenId;696697        const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));698        const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));699        const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));700701        switch (testCase) {702          case 'transfer':703            await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});704            break;705          case 'transferCross':706            await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});707            break;708          case 'transferFrom':709            await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});710            break;711          case 'transferFromCross':712            await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});713            break;714        }715716        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));717        expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);718        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));719        expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;720        const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));721        expect(userBalanceAfter).to.be.eq(userBalanceBefore);722      }));723  });724725  describe('Collection admins', () => {726    let donor: IKeyringPair;727728    before(async function() {729      await usingEthPlaygrounds(async (_helper, privateKey) => {730        donor = await privateKey({url: import.meta.url});731      });732    });733734    [735      {mode: 'nft' as const, requiredPallets: []},736      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},737      {mode: 'ft' as const, requiredPallets: []},738    ].map(testCase => {739      itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {740        // arrange741        const owner = await helper.eth.createAccountWithBalance(donor);742        const adminSub = await privateKey('//admin2');743        const adminEth = helper.eth.createAccount().toLowerCase();744745        const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);746        const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);747748        const {collectionAddress, collectionId} = await helper.eth.createCollection(749          owner,750          {751            ...CREATE_COLLECTION_DATA_DEFAULTS,752            name: 'Mint collection',753            description: 'a',754            tokenPrefix: 'b',755            collectionMode: testCase.mode,756            adminList: [adminCrossSub, adminCrossEth],757          },758        ).send();759        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);760761        // 1. Expect api.rpc.unique.adminlist returns admins:762        const adminListRpc = await helper.collection.getAdmins(collectionId);763        expect(adminListRpc).to.has.length(2);764        expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);765766        // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist767        let adminListEth = await collectionEvm.methods.collectionAdmins().call();768        adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));769        expect(adminListRpc).to.be.like(adminListEth);770771        // 3. check isOwnerOrAdminCross returns true:772        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;773        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;774      });775    });776777    itEth('cross account admin can mint', async ({helper}) => {778      // arrange: create collection and accounts779      const owner = await helper.eth.createAccountWithBalance(donor);780      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();781      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);782      const [adminSub] = await helper.arrange.createAccounts([100n], donor);783      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);784      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(785        owner,786        {787          ...CREATE_COLLECTION_DATA_DEFAULTS,788          name: 'Mint collection',789          description: 'a',790          tokenPrefix: 'b',791          collectionMode: 'nft',792          adminList: [adminCrossSub, adminCrossEth],793        },794        'uri',795      );796      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);797798      // admin (sub and eth) can mint token:799      await collectionEvm.methods.mint(owner).send({from: adminEth});800      await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});801802      expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);803    });804805    itEth('cannot add invalid cross account admin', async ({helper}) => {806      const owner = await helper.eth.createAccountWithBalance(donor);807      const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);808809      const adminCross = {810        eth: helper.address.substrateToEth(admin.address),811        sub: admin.addressRaw,812      };813814      await expect(helper.eth.createCollection(815        owner,816        {817          ...CREATE_COLLECTION_DATA_DEFAULTS,818          name: 'A',819          description: 'B',820          tokenPrefix: 'C',821          collectionMode: 'nft',822          adminList: [adminCross],823        },824      ).call()).to.be.rejected;825    });826827    itEth('Remove [cross] admin by owner', async ({helper}) => {828      const owner = await helper.eth.createAccountWithBalance(donor);829830      const [adminSub] = await helper.arrange.createAccounts([10n], donor);831      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();832      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);833      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);834835      const {collectionAddress, collectionId} = await helper.eth.createCollection(836        owner,837        {838          ...CREATE_COLLECTION_DATA_DEFAULTS,839          name: 'A',840          description: 'B',841          tokenPrefix: 'C',842          collectionMode: 'nft',843          adminList: [adminCrossSub, adminCrossEth],844        },845      ).send();846847      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);848849      {850        const adminList = await helper.collection.getAdmins(collectionId);851        expect(adminList).to.deep.include({Substrate: adminSub.address});852        expect(adminList).to.deep.include({Ethereum: adminEth});853      }854855      await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();856      await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();857      const adminList = await helper.collection.getAdmins(collectionId);858      expect(adminList.length).to.be.eq(0);859860      // Non admin cannot mint:861      await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);862      await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;863    });864  });865866  describe('Collection limits', () => {867    describe('Can set collection limits', () => {868      [869        {case: 'nft' as const},870        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},871        {case: 'ft' as const},872      ].map(testCase =>873        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {874          const owner = await helper.eth.createAccountWithBalance(donor);875876          const limits = {877            accountTokenOwnershipLimit: 1000n,878            sponsoredDataSize: 1024n,879            sponsoredDataRateLimit: 30n,880            tokenLimit: 1000000n,881            sponsorTransferTimeout: 6n,882            sponsorApproveTimeout: 6n,883            ownerCanTransfer: 1n,884            ownerCanDestroy: 0n,885            transfersEnabled: 0n,886          };887888          const {collectionId, collectionAddress} = await helper.eth.createCollection(889            owner,890            {891              ...CREATE_COLLECTION_DATA_DEFAULTS,892              name: 'Limits',893              description: 'absolutely anything',894              tokenPrefix: 'FLO',895              collectionMode: testCase.case,896              limits: [897                {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},898                {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},899                {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},900                {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},901                {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},902                {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},903                {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},904                {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},905                {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},906              ],907            },908          ).send();909910          const expectedLimits = {911            accountTokenOwnershipLimit: 1000,912            sponsoredDataSize: 1024,913            sponsoredDataRateLimit: {blocks: 30},914            tokenLimit: 1000000,915            sponsorTransferTimeout: 6,916            sponsorApproveTimeout: 6,917            ownerCanTransfer: true,918            ownerCanDestroy: false,919            transfersEnabled: false,920          };921922          const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);923924          // Check limits from sub:925          const data = (await helper.rft.getData(collectionId))!;926          expect(data.raw.limits).to.deep.eq(expectedLimits);927          expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);928          // Check limits from eth:929          const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});930          expect(limitsEvm).to.have.length(9);931          expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);932          expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);933          expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);934          expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);935          expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);936          expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);937          expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);938          expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);939          expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);940        }));941    });942943    describe('(!negative test!) Cannot set invalid collection limits', () => {944      [945        {case: 'nft' as const},946        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},947        {case: 'ft' as const},948      ].map(testCase =>949        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {950          const invalidLimits = {951            accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),952            transfersEnabled: 3,953          };954955          const createCollectionData = {956            ...CREATE_COLLECTION_DATA_DEFAULTS,957            name: 'Limits',958            description: 'absolutely anything',959            tokenPrefix: 'ISNI',960            collectionMode: testCase.case,961          };962963          const owner = await helper.eth.createAccountWithBalance(donor);964          await expect(helper.eth.createCollection(965            owner,966            {967              ...createCollectionData,968              limits: [{field: 9 as CollectionLimitField, value: 1n}],969            },970          ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');971972          await expect(helper.eth.createCollection(973            owner,974            {975              ...createCollectionData,976              limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],977            },978          ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);979980          await expect(helper.eth.createCollection(981            owner,982            {983              ...createCollectionData,984              limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],985            },986          ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);987988          await expect(helper.eth.createCollection(989            owner,990            {991              ...createCollectionData,992              limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],993            },994          ).call()).to.be.rejectedWith('value out-of-bounds');995        }));996    });997  });998999  describe('Collection properties', () => {10001001    [1002      {mode: 'nft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1003      {mode: 'rft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1004      {mode: 'ft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]},1005    ].map(testCase =>1006      itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1007        const caller = await helper.eth.createAccountWithBalance(donor);1008        const callerCross = helper.ethCrossAccount.fromAddress(caller);1009        const owner = await helper.eth.createAccountWithBalance(donor);1010        const {collectionId, collectionAddress} = await helper.eth.createCollection(1011          owner,1012          {1013            ...CREATE_COLLECTION_DATA_DEFAULTS,1014            name: 'name',1015            description: 'test',1016            tokenPrefix: 'test',1017            collectionMode: testCase.mode,1018            adminList: [callerCross],1019            properties: testCase.methodParams,1020          },1021        ).send();10221023        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10241025        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1026        expect(raw.properties).to.deep.equal(testCase.expectedProps);10271028        // collectionProperties returns properties:1029        expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1030      }));10311032    [1033      {mode: 'nft' as const},1034      {mode: 'rft' as const},1035      {mode: 'ft' as const},1036    ].map(testCase =>1037      itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1038        const caller = await helper.eth.createAccountWithBalance(donor);1039        const callerCross = helper.ethCrossAccount.fromAddress(caller);1040        const owner = await helper.eth.createAccountWithBalance(donor);1041        const {collectionId, collectionAddress} = await helper.eth.createCollection(1042          owner,1043          {1044            ...CREATE_COLLECTION_DATA_DEFAULTS,1045            name: 'name',1046            description: 'test',1047            tokenPrefix: 'test',1048            collectionMode: testCase.mode,1049            adminList: [callerCross],1050            properties:[1051              {key: 'testKey1', value: Buffer.from('testValue1')},1052              {key: 'testKey2', value: Buffer.from('testValue2')},1053              {key: 'testKey3', value: Buffer.from('testValue3')}],1054          },1055        ).send();10561057        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10581059        await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10601061        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10621063        expect(raw.properties.length).to.equal(1);1064        expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1065      }));10661067    itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1068      const caller = await helper.eth.createAccountWithBalance(donor);1069      const callerCross = helper.ethCrossAccount.fromAddress(caller);1070      const owner = await helper.eth.createAccountWithBalance(donor);1071      const createCollectionData = {1072        ...CREATE_COLLECTION_DATA_DEFAULTS,1073        name: 'name',1074        description: 'test',1075        tokenPrefix: 'test',1076        collectionMode: 'nft' as TCollectionMode,1077        adminList: [callerCross],1078      };1079      await expect(helper.eth.createCollection(1080        owner,1081        {1082          ...createCollectionData,1083          properties: [{key: '', value: Buffer.from('val1')}],1084        },1085      ).call()).to.be.rejected;10861087      await expect(helper.eth.createCollection(1088        owner,1089        {1090          ...createCollectionData,1091          properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1092        },1093      ).call()).to.be.rejected;10941095      await expect(helper.eth.createCollection(1096        owner,1097        {1098          ...createCollectionData,1099          properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1100        },1101      ).call()).to.be.rejected;1102    });11031104    itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1105      const caller = await helper.eth.createAccountWithBalance(donor);1106      const owner = await helper.eth.createAccountWithBalance(donor);1107      const {collectionAddress} = await helper.eth.createCollection(1108        owner,1109        {1110          ...CREATE_COLLECTION_DATA_DEFAULTS,1111          name: 'name',1112          description: 'test',1113          tokenPrefix: 'test',1114          collectionMode: 'nft',1115          properties:[1116            {key: 'testKey1', value: Buffer.from('testValue1')},1117            {key: 'testKey2', value: Buffer.from('testValue2')}],1118        },1119      ).send();11201121      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11221123      await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1124    });1125  });11261127  describe('Token property permissions', () => {1128    [1129      {mode: 'nft' as const, requiredPallets: []},1130      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1131    ].map(testCase =>1132      itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1133        const owner = await helper.eth.createAccountWithBalance(donor);1134        const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1135        for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1136          const {collectionId, collectionAddress} = await helper.eth.createCollection(1137            owner,1138            {1139              ...CREATE_COLLECTION_DATA_DEFAULTS,1140              name: 'A',1141              description: 'B',1142              tokenPrefix: 'C',1143              collectionMode: testCase.mode,1144              adminList: [caller],1145              tokenPropertyPermissions: [1146                {1147                  key: 'testKey',1148                  permissions: [1149                    {code: TokenPermissionField.Mutable, value: mutable},1150                    {code: TokenPermissionField.TokenOwner, value: tokenOwner},1151                    {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1152                  ],1153                },1154              ],1155            },1156          ).send();1157          const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11581159          expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1160            key: 'testKey',1161            permission: {mutable, collectionAdmin, tokenOwner},1162          }]);11631164          expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1165            ['testKey', [1166              [TokenPermissionField.Mutable.toString(), mutable],1167              [TokenPermissionField.TokenOwner.toString(), tokenOwner],1168              [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1169            ],1170          ]);1171        }1172      }));11731174    [1175      {mode: 'nft' as const, requiredPallets: []},1176      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1177    ].map(testCase =>1178      itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1179        const owner = await helper.eth.createAccountWithBalance(donor);1180        const {collectionId, collectionAddress} = await helper.eth.createCollection(1181          owner,1182          {1183            ...CREATE_COLLECTION_DATA_DEFAULTS,1184            name: 'A',1185            description: 'B',1186            tokenPrefix: 'C',1187            collectionMode: testCase.mode,1188            tokenPropertyPermissions: [1189              {1190                key: 'testKey_0',1191                permissions: [1192                  {code: TokenPermissionField.Mutable, value: true},1193                  {code: TokenPermissionField.TokenOwner, value: true},1194                  {code: TokenPermissionField.CollectionAdmin, value: true}],1195              },1196              {1197                key: 'testKey_1',1198                permissions: [1199                  {code: TokenPermissionField.Mutable, value: true},1200                  {code: TokenPermissionField.TokenOwner, value: false},1201                  {code: TokenPermissionField.CollectionAdmin, value: true}],1202              },1203              {1204                key: 'testKey_2',1205                permissions: [1206                  {code: TokenPermissionField.Mutable, value: false},1207                  {code: TokenPermissionField.TokenOwner, value: true},1208                  {code: TokenPermissionField.CollectionAdmin, value: false}],1209              },1210            ],1211          },1212        ).send();1213        const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12141215        expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1216          {1217            key: 'testKey_0',1218            permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1219          },1220          {1221            key: 'testKey_1',1222            permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1223          },1224          {1225            key: 'testKey_2',1226            permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1227          },1228        ]);12291230        expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1231          ['testKey_0', [1232            [TokenPermissionField.Mutable.toString(), true],1233            [TokenPermissionField.TokenOwner.toString(), true],1234            [TokenPermissionField.CollectionAdmin.toString(), true]],1235          ],1236          ['testKey_1', [1237            [TokenPermissionField.Mutable.toString(), true],1238            [TokenPermissionField.TokenOwner.toString(), false],1239            [TokenPermissionField.CollectionAdmin.toString(), true]],1240          ],1241          ['testKey_2', [1242            [TokenPermissionField.Mutable.toString(), false],1243            [TokenPermissionField.TokenOwner.toString(), true],1244            [TokenPermissionField.CollectionAdmin.toString(), false]],1245          ],1246        ]);1247      }));12481249    [1250      {mode: 'nft' as const, requiredPallets: []},1251      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1252    ].map(testCase =>1253      itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1254        const caller = await helper.eth.createAccountWithBalance(donor);1255        const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1256        const {collectionAddress} = await helper.eth.createCollection(1257          caller,1258          {1259            ...CREATE_COLLECTION_DATA_DEFAULTS,1260            name: 'A',1261            description: 'B',1262            tokenPrefix: 'C',1263            collectionMode: testCase.mode,1264            adminList: [receiver],1265            tokenPropertyPermissions: [1266              {1267                key: 'testKey',1268                permissions: [1269                  {code: TokenPermissionField.Mutable, value: true},1270                  {code: TokenPermissionField.CollectionAdmin, value: true}],1271              },1272              {1273                key: 'testKey_1',1274                permissions: [1275                  {code: TokenPermissionField.Mutable, value: true},1276                  {code: TokenPermissionField.CollectionAdmin, value: true}],1277              },1278            ],1279          },1280        ).send();12811282        const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1283        const tokenId = (await collection.methods.mintCross(receiver, [{key: 'testKey', value: Buffer.from('testValue')}, {key: 'testKey_1', value: Buffer.from('testValue_1')}]).send()).events.Transfer.returnValues.tokenId;1284        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12851286        await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1287        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1288      }));1289  });12901291  describe('Nesting', () => {1292    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1293      const owner = await helper.eth.createAccountWithBalance(donor);1294      const {collectionAddress, collectionId} = await helper.eth.createCollection(1295        owner,1296        {1297          ...CREATE_COLLECTION_DATA_DEFAULTS,1298          name: 'A',1299          description: 'B',1300          tokenPrefix: 'C',1301          collectionMode: 'nft',1302          nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1303        },1304      ).send();13051306      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13071308      // Create a token to be nested to1309      const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1310      const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1311      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13121313      // Create a nested token1314      const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1315      const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1316      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13171318      // Create a token to be nested and nest1319      const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1320      const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13211322      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1323      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13241325      // Unnest token back1326      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1327      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1328    });13291330    itEth('NFT: collectionNesting()', async ({helper}) => {1331      const owner = await helper.eth.createAccountWithBalance(donor);1332      const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1333        owner,1334        new CreateCollectionData('A', 'B', 'C', 'nft'),1335      ).send();13361337      const unnestedContract = helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1338      expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13391340      const {collectionAddress} = await helper.eth.createCollection(1341        owner,1342        {1343          ...CREATE_COLLECTION_DATA_DEFAULTS,1344          name: 'A',1345          description: 'B',1346          tokenPrefix: 'C',1347          collectionMode: 'nft',1348          nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1349        },1350      ).send();13511352      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1353      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1354      await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1355      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1356    });13571358    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1359      const owner = await helper.eth.createAccountWithBalance(donor);13601361      const {collectionId, collectionAddress} = await helper.eth.createCollection(1362        owner,1363        {1364          ...CREATE_COLLECTION_DATA_DEFAULTS,1365          name: 'A',1366          description: 'B',1367          tokenPrefix: 'C',1368          collectionMode: 'nft',1369          nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1370        },1371      ).send();13721373      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13741375      // Create a token to nest into1376      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1377      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1378      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13791380      // Create a token to nest1381      const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1382      const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13831384      // Try to nest1385      await expect(contract.methods1386        .transfer(targetNftTokenAddress, nftTokenId)1387        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1388    });1389  });13901391  describe('Flags', () => {1392    const createCollectionData = {1393      ...CREATE_COLLECTION_DATA_DEFAULTS,1394      name: 'A',1395      description: 'B',1396      tokenPrefix: 'C',1397      collectionMode: 'nft' as TCollectionMode,1398    };13991400    itEth('NFT: use numbers for flags', async ({helper}) => {1401      const owner = await helper.eth.createAccountWithBalance(donor);14021403      {1404        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1405        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1406      }14071408      {1409        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1410        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1411      }1412    });14131414    itEth('NFT: can\'t set foreign flag number', async ({helper}) => {1415      const owner = await helper.eth.createAccountWithBalance(donor);14161417      {1418        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1419      }14201421      {1422        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1423      }1424    });14251426    itEth('NFT: use enum for flags', async ({helper}) => {1427      const owner = await helper.eth.createAccountWithBalance(donor);14281429      {1430        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1431        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1432      }1433    });14341435    itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1436      const owner = await helper.eth.createAccountWithBalance(donor);14371438      {1439        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1440      }14411442      {1443        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1444      }1445    });1446  });1447});
modifiedtests/src/eth/createFTCollection.seqtest.tsdiffbeforeafterboth
--- a/tests/src/eth/createFTCollection.seqtest.ts
+++ b/tests/src/eth/createFTCollection.seqtest.ts
@@ -59,7 +59,7 @@
 
     const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
     const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
-    const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
     expect(await collectionHelpers.methods
       .isCollectionExist(expectedCollectionAddress)
modifiedtests/src/eth/createFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createFTCollection.test.ts
+++ b/tests/src/eth/createFTCollection.test.ts
@@ -50,7 +50,7 @@
 
     await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');
 
-    const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
+    const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
 
     data = (await helper.rft.getData(collectionId))!;
@@ -64,7 +64,7 @@
     const description = 'absolutely anything';
     const {collectionId, collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', DECIMALS, description, 'ENVY');
 
-    const collection = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
     await collection.methods.setCollectionSponsorCross(sponsorCross).send();
 
@@ -73,7 +73,7 @@
 
     await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');
 
-    const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
+    const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
 
     data = (await helper.rft.getData(collectionId))!;
@@ -84,7 +84,7 @@
   itEth('Collection address exist', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
-    const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
     expect(await collectionHelpers
       .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
@@ -96,7 +96,7 @@
       .to.be.true;
 
     // check collectionOwner:
-    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);
+    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);
     const collectionOwner = await collectionEvm.methods.collectionOwner().call();
     expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));
   });
@@ -104,7 +104,7 @@
   itEth('destroyCollection', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const {collectionAddress, collectionId} = await helper.eth.createFungibleCollection(owner, 'Exister', DECIMALS, 'absolutely anything', 'WIWT');
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
 
     const result = await collectionHelper.methods
       .destroyCollection(collectionAddress)
@@ -143,7 +143,7 @@
 
   itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     {
       const MAX_NAME_LENGTH = 64;
       const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);
@@ -176,7 +176,7 @@
 
   itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     const expects = [0n, 1n, 30n].map(async value => {
       await expect(collectionHelper.methods
         .createFTCollection('Peasantry', DECIMALS, 'absolutely anything', 'TWIW')
@@ -190,7 +190,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const peasant = helper.eth.createAccount();
     const {collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Transgressed', DECIMALS, 'absolutely anything', 'YVNE');
-    const peasantCollection = await helper.ethNativeContract.collection(collectionAddress, 'ft', peasant, true);
+    const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'ft', peasant, true);
     const EXPECTED_ERROR = 'NoPermission';
     {
       const sponsor = await helper.eth.createAccountWithBalance(donor);
@@ -198,7 +198,7 @@
         .setCollectionSponsor(sponsor)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
 
-      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'ft', sponsor, true);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'ft', sponsor, true);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('ConfirmSponsorshipFail');
@@ -214,7 +214,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const peasant = helper.eth.createAccount();
     const {collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Transgressed', DECIMALS, 'absolutely anything', 'YVNE');
-    const peasantCollection = await helper.ethNativeContract.collection(collectionAddress, 'ft', peasant);
+    const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'ft', peasant);
     const EXPECTED_ERROR = 'NoPermission';
     {
       const sponsor = await helper.eth.createAccountWithBalance(donor);
@@ -223,7 +223,7 @@
         .setCollectionSponsorCross(sponsorCross)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
 
-      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'ft', sponsor);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'ft', sponsor);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('ConfirmSponsorshipFail');
modifiedtests/src/eth/createNFTCollection.seqtest.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.seqtest.ts
+++ b/tests/src/eth/createNFTCollection.seqtest.ts
@@ -72,7 +72,7 @@
 
     const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
     const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
-    const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
     expect(await collectionHelpers.methods
       .isCollectionExist(expectedCollectionAddress)
modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -39,7 +39,7 @@
     const baseUri = 'BaseURI';
 
     const {collectionId, collectionAddress, events} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, name, description, prefix, baseUri);
-    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');
 
     expect(events).to.be.deep.equal([
       {
@@ -82,7 +82,7 @@
     const ss58Format = helper.chain.getChainProperties().ss58Format;
     const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
 
-    const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);
     await collection.methods.setCollectionSponsor(sponsor).send();
 
     let data = (await helper.nft.getData(collectionId))!;
@@ -90,7 +90,7 @@
 
     await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');
 
-    const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor, true);
+    const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor, true);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
 
     data = (await helper.nft.getData(collectionId))!;
@@ -104,7 +104,7 @@
     const description = 'absolutely anything';
     const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', description, 'ROC');
 
-    const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
     const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
     await collection.methods.setCollectionSponsorCross(sponsorCross).send();
 
@@ -125,7 +125,7 @@
   itEth('Collection address exist', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
-    const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
     expect(await collectionHelpers
       .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
@@ -137,7 +137,7 @@
       .to.be.true;
 
     // check collectionOwner:
-    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);
+    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);
     const collectionOwner = await collectionEvm.methods.collectionOwner().call();
     expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));
   });
@@ -156,7 +156,7 @@
 
   itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     {
       const MAX_NAME_LENGTH = 64;
       const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);
@@ -190,7 +190,7 @@
 
   itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     await expect(collectionHelper.methods
       .createNFTCollection('Peasantry', 'absolutely anything', 'CVE')
       .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
@@ -209,7 +209,7 @@
         .setCollectionSponsor(sponsor)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
 
-      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor, true);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor, true);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('ConfirmSponsorshipFail');
@@ -225,7 +225,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const malfeasant = helper.eth.createAccount();
     const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Transgressed', 'absolutely anything', 'COR');
-    const malfeasantCollection = await helper.ethNativeContract.collection(collectionAddress, 'nft', malfeasant);
+    const malfeasantCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', malfeasant);
     const EXPECTED_ERROR = 'NoPermission';
     {
       const sponsor = await helper.eth.createAccountWithBalance(donor);
@@ -234,7 +234,7 @@
         .setCollectionSponsorCross(sponsorCross)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
 
-      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('ConfirmSponsorshipFail');
@@ -249,7 +249,7 @@
   itEth('destroyCollection', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
 
 
     const result = await collectionHelper.methods
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -95,7 +95,7 @@
     const ss58Format = helper.chain.getChainProperties().ss58Format;
     const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
 
-    const collection = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner, true);
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner, true);
     await collection.methods.setCollectionSponsor(sponsor).send();
 
     let data = (await helper.rft.getData(collectionId))!;
@@ -103,7 +103,7 @@
 
     await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');
 
-    const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
+    const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
 
     data = (await helper.rft.getData(collectionId))!;
@@ -116,7 +116,7 @@
     const ss58Format = helper.chain.getChainProperties().ss58Format;
     const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ENVY');
 
-    const collection = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
     const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);
     await collection.methods.setCollectionSponsorCross(sponsorCross).send();
 
@@ -125,7 +125,7 @@
 
     await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');
 
-    const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
+    const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
     await sponsorCollection.methods.confirmCollectionSponsorship().send();
 
     data = (await helper.rft.getData(collectionId))!;
@@ -135,7 +135,7 @@
   itEth('Collection address exist', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';
-    const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
 
     expect(await collectionHelpers
       .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())
@@ -147,7 +147,7 @@
       .to.be.true;
 
     // check collectionOwner:
-    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);
+    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);
     const collectionOwner = await collectionEvm.methods.collectionOwner().call();
     expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));
   });
@@ -167,7 +167,7 @@
 
   itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     {
       const MAX_NAME_LENGTH = 64;
       const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);
@@ -200,7 +200,7 @@
 
   itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
     await expect(collectionHelper.methods
       .createRFTCollection('Peasantry', 'absolutely anything', 'TWIW')
       .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');
@@ -211,7 +211,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const peasant = helper.eth.createAccount();
     const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
-    const peasantCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', peasant, true);
+    const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', peasant, true);
     const EXPECTED_ERROR = 'NoPermission';
     {
       const sponsor = await helper.eth.createAccountWithBalance(donor);
@@ -219,7 +219,7 @@
         .setCollectionSponsor(sponsor)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
 
-      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('ConfirmSponsorshipFail');
@@ -235,7 +235,7 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const peasant = helper.eth.createAccount();
     const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Transgressed', 'absolutely anything', 'YVNE');
-    const peasantCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', peasant);
+    const peasantCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', peasant);
     const EXPECTED_ERROR = 'NoPermission';
     {
       const sponsor = await helper.eth.createAccountWithBalance(donor);
@@ -244,7 +244,7 @@
         .setCollectionSponsorCross(sponsorCross)
         .call()).to.be.rejectedWith(EXPECTED_ERROR);
 
-      const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
+      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);
       await expect(sponsorCollection.methods
         .confirmCollectionSponsorship()
         .call()).to.be.rejectedWith('ConfirmSponsorshipFail');
@@ -259,7 +259,7 @@
   itEth('destroyCollection', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const {collectionAddress, collectionId} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
-    const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner);
+    const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
 
     await expect(collectionHelper.methods
       .destroyCollection(collectionAddress)
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -207,7 +207,7 @@
   }
 
   private async createTransaction() {
-    const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(this.signer);
+    const collectionHelper = this.helper.ethNativeContract.collectionHelpers(this.signer);
     let collectionMode;
     switch (this.data.collectionMode) {
       case 'nft': collectionMode = CollectionMode.Nonfungible; break;
modifiedtests/src/util/index.tsdiffbeforeafterboth
--- a/tests/src/util/index.ts
+++ b/tests/src/util/index.ts
@@ -15,6 +15,7 @@
 import {dirname} from 'path';
 import {fileURLToPath} from 'url';
 
+chai.config.truncateThreshold = 0;
 chai.use(chaiAsPromised);
 chai.use(chaiSubset);
 export const expect = chai.expect;
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -41,7 +41,9 @@
       for(const arg of args) {
         if(typeof arg !== 'string')
           continue;
-        if(arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')
+        const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis'];
+        const needToSkip = skippedWarnings.reduce((a,  b) => a || arg.includes(b), false);
+        if(needToSkip || arg === 'Normal connection closure')
           return;
       }
       printer(...args);