git.delta.rocks / unique-network / refs/commits / 2332519c8f48

difftreelog

source

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