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

difftreelog

source

js-packages/tests/eth/createCollection.test.ts63.4 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 type {IKeyringPair} from '@polkadot/types/types';18import {evmToAddress} from '@polkadot/util-crypto';19import {Pallets, requirePalletsOrSkip} from '../util/index.js';20import {expect, itEth, usingEthPlaygrounds} from './util/index.js';21import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types.js';22import {CollectionFlag} from '@unique/playgrounds/types.js';23import type {IEthCrossAccountId, TCollectionMode} from '@unique/playgrounds/types.js';2425const DECIMALS = 18;26const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [27  [],  // properties28  [],  // tokenPropertyPermissions29  [],  // adminList30  [false, false, []],  // nestingSettings31  [],  // limits32  emptyAddress,  // pendingSponsor33  [0],  // flags34];3536type ElementOf<A> = A extends readonly (infer T)[] ? T : never;37function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {38  if(args.length === 0) {39    yield internalRest as any;40    return;41  }42  for(const value of args[0]) {43    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;44  }45}4647describe('Create collection from EVM', () => {48  let donor: IKeyringPair;49  let nominal: bigint;5051  before(async function() {52    await usingEthPlaygrounds(async (helper, privateKey) => {53      donor = await privateKey({url: import.meta.url});54      nominal = helper.balance.getOneTokenNominal();55    });56  });5758  describe('Fungible collection', () => {59    before(async function() {60      await usingEthPlaygrounds((helper) => {61        requirePalletsOrSkip(this, helper, [Pallets.Fungible]);62        return Promise.resolve();63      });64    });6566    itEth('Collection address exist', async ({helper}) => {67      const owner = await helper.eth.createAccountWithBalance(donor);68      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';69      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);7071      expect(await collectionHelpers72        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())73        .to.be.false;7475      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();76      expect(await collectionHelpers77        .methods.isCollectionExist(collectionAddress).call())78        .to.be.true;7980      // check collectionOwner:81      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);82      const collectionOwner = await collectionEvm.methods.collectionOwner().call();83      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));84    });8586    itEth('destroyCollection', async ({helper}) => {87      const owner = await helper.eth.createAccountWithBalance(donor);88      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send();89      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);9091      const result = await collectionHelper.methods92        .destroyCollection(collectionAddress)93        .send({from: owner});9495      const events = helper.eth.normalizeEvents(result.events);9697      expect(events).to.be.deep.equal([98        {99          address: collectionHelper.options.address,100          event: 'CollectionDestroyed',101          args: {102            collectionId: collectionAddress,103          },104        },105      ]);106107      expect(await collectionHelper.methods108        .isCollectionExist(collectionAddress)109        .call()).to.be.false;110      expect(await helper.collection.getData(collectionId)).to.be.null;111    });112113    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {114      const owner = await helper.eth.createAccountWithBalance(donor);115      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);116      {117        const MAX_NAME_LENGTH = 64;118        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);119        const description = 'A';120        const tokenPrefix = 'A';121122        await expect(collectionHelper.methods123          .createCollection([124            collectionName,125            description,126            tokenPrefix,127            CollectionMode.Fungible,128            DECIMALS,129            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,130          ])131          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);132      }133      {134        const MAX_DESCRIPTION_LENGTH = 256;135        const collectionName = 'A';136        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);137        const tokenPrefix = 'A';138        await expect(collectionHelper.methods139          .createCollection([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            collectionName,157            description,158            tokenPrefix,159            CollectionMode.Fungible,160            DECIMALS,161            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,162          ])163          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);164      }165    });166167    itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => {168      const owner = await helper.eth.createAccountWithBalance(donor);169      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);170      const expects = [0n, 1n, 30n].map(async value => {171        await expect(collectionHelper.methods172          .createCollection([173            'Peasantry',174            'absolutely anything',175            'TWIW',176            CollectionMode.Fungible,177            DECIMALS,178            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,179          ])180          .call({value: Number(value * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');181      });182      await Promise.all(expects);183    });184  });185186  describe('Nonfungible collection', () => {187    before(async function() {188      await usingEthPlaygrounds((helper) => {189        requirePalletsOrSkip(this, helper, [Pallets.NFT]);190        return Promise.resolve();191      });192    });193194    itEth('Create collection', async ({helper}) => {195      const owner = await helper.eth.createAccountWithBalance(donor);196197      const name = 'CollectionEVM';198      const description = 'Some description';199      const prefix = 'token prefix';200201      // todo:playgrounds this might fail when in async environment.202      const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;203      const {collectionId, collectionAddress, events} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'nft')).send();204205      expect(events).to.be.deep.equal([206        {207          address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',208          event: 'CollectionCreated',209          args: {210            owner: owner,211            collectionId: collectionAddress,212          },213        },214      ]);215216      const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;217218      const collection = helper.nft.getCollectionObject(collectionId);219      const data = (await collection.getData())!;220221      // Parallel test safety222      expect(collectionCountAfter - collectionCountBefore).to.be.gte(1);223      expect(collectionId).to.be.eq(collectionCountAfter);224      expect(data.name).to.be.eq(name);225      expect(data.description).to.be.eq(description);226      expect(data.raw.tokenPrefix).to.be.eq(prefix);227      expect(data.raw.mode).to.be.eq('NFT');228229      const options = await collection.getOptions();230231      expect(options.tokenPropertyPermissions).to.be.empty;232    });233234    // this test will occasionally fail when in async environment.235    itEth('Check collection address exist', async ({helper}) => {236      const owner = await helper.eth.createAccountWithBalance(donor);237238      const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;239      const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);240      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);241242      expect(await collectionHelpers.methods243        .isCollectionExist(expectedCollectionAddress)244        .call()).to.be.false;245246      await collectionHelpers.methods247        .createCollection([248          'A',249          'A',250          'A',251          CollectionMode.Nonfungible,252          0,253          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,254        ])255        .send({value: Number(2n * helper.balance.getOneTokenNominal())});256257      expect(await collectionHelpers.methods258        .isCollectionExist(expectedCollectionAddress)259        .call()).to.be.true;260    });261  });262263  describe('Create RFT collection from EVM', () => {264    before(async function() {265      await usingEthPlaygrounds((helper) => {266        requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);267        return Promise.resolve();268      });269    });270271    itEth('Create collection', async ({helper}) => {272      const owner = await helper.eth.createAccountWithBalance(donor);273274      const name = 'CollectionEVM';275      const description = 'Some description';276      const prefix = 'token prefix';277278      const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send();279      const data = (await helper.rft.getData(collectionId))!;280      const collection = helper.rft.getCollectionObject(collectionId);281282      expect(data.name).to.be.eq(name);283      expect(data.description).to.be.eq(description);284      expect(data.raw.tokenPrefix).to.be.eq(prefix);285      expect(data.raw.mode).to.be.eq('ReFungible');286287      const options = await collection.getOptions();288289      expect(options.tokenPropertyPermissions).to.be.empty;290    });291292    itEth('Create collection with properties & get description', async ({helper}) => {293      const owner = await helper.eth.createAccountWithBalance(donor);294295      const name = 'CollectionEVM';296      const description = 'Some description';297      const prefix = 'token prefix';298      const baseUri = 'BaseURI';299300      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri);301      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');302303      const collection = helper.rft.getCollectionObject(collectionId);304      const data = (await collection.getData())!;305306      expect(data.name).to.be.eq(name);307      expect(data.description).to.be.eq(description);308      expect(data.raw.tokenPrefix).to.be.eq(prefix);309      expect(data.raw.mode).to.be.eq('ReFungible');310311      expect(await contract.methods.description().call()).to.deep.equal(description);312313      const options = await collection.getOptions();314      expect(options.tokenPropertyPermissions).to.be.deep.equal([315        {316          key: 'URI',317          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},318        },319        {320          key: 'URISuffix',321          permission: {mutable: true, collectionAdmin: true, tokenOwner: false},322        },323      ]);324    });325326    itEth('Set sponsorship', async ({helper}) => {327      const owner = await helper.eth.createAccountWithBalance(donor);328      const sponsor = await helper.eth.createAccountWithBalance(donor);329      const ss58Format = helper.chain.getChainProperties().ss58Format;330      const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send();331332      const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);333      const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor);334      await collection.methods.setCollectionSponsorCross(sponsorCross).send();335336      let data = (await helper.rft.getData(collectionId))!;337      expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));338339      await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');340341      const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor);342      await sponsorCollection.methods.confirmCollectionSponsorship().send();343344      data = (await helper.rft.getData(collectionId))!;345      expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format)));346    });347348    itEth('Collection address exist', async ({helper}) => {349      const owner = await helper.eth.createAccountWithBalance(donor);350      const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233';351      const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);352353      expect(await collectionHelpers354        .methods.isCollectionExist(collectionAddressForNonexistentCollection).call())355        .to.be.false;356357      const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send();358      expect(await collectionHelpers359        .methods.isCollectionExist(collectionAddress).call())360        .to.be.true;361362      // check collectionOwner:363      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);364      const collectionOwner = await collectionEvm.methods.collectionOwner().call();365      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true));366    });367368    itEth('destroyCollection', async ({helper}) => {369      const owner = await helper.eth.createAccountWithBalance(donor);370      const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send();371      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);372373      await expect(collectionHelper.methods374        .destroyCollection(collectionAddress)375        .send({from: owner})).to.be.fulfilled;376377      expect(await collectionHelper.methods378        .isCollectionExist(collectionAddress)379        .call()).to.be.false;380      expect(await helper.collection.getData(collectionId)).to.be.null;381    });382383    itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => {384      const owner = await helper.eth.createAccountWithBalance(donor);385      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);386      {387        const MAX_NAME_LENGTH = 64;388        const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1);389        const description = 'A';390        const tokenPrefix = 'A';391392        await expect(collectionHelper.methods393          .createCollection([394            collectionName,395            description,396            tokenPrefix,397            CollectionMode.Refungible,398            DECIMALS,399            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,400          ])401          .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH);402      }403      {404        const MAX_DESCRIPTION_LENGTH = 256;405        const collectionName = 'A';406        const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1);407        const tokenPrefix = 'A';408        await expect(collectionHelper.methods409          .createCollection([410            collectionName,411            description,412            tokenPrefix,413            CollectionMode.Refungible,414            DECIMALS,415            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,416          ])417          .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH);418      }419      {420        const MAX_TOKEN_PREFIX_LENGTH = 16;421        const collectionName = 'A';422        const description = 'A';423        const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1);424        await expect(collectionHelper.methods425          .createCollection([426            collectionName,427            description,428            tokenPrefix,429            CollectionMode.Refungible,430            DECIMALS,431            ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,432          ])433          .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH);434      }435    });436437    itEth('(!negative test!) Create collection (no funds)', async ({helper}) => {438      const owner = await helper.eth.createAccountWithBalance(donor);439      const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);440      await expect(collectionHelper.methods441        .createCollection([442          'Peasantry',443          'absolutely anything',444          'TWIW',445          CollectionMode.Refungible,446          0,447          ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY,448        ])449        .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)');450    });451  });452453  describe('Sponsoring', () => {454    itEth('Сan remove collection sponsor', async ({helper}) => {455      const owner = await helper.eth.createAccountWithBalance(donor);456      const sponsor = await helper.eth.createAccountWithBalance(donor);457      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);458459      const {collectionAddress} = await helper.eth.createCollection(460        owner,461        {462          ...CREATE_COLLECTION_DATA_DEFAULTS,463          name: 'Sponsor collection',464          description: '1',465          tokenPrefix: '1',466          collectionMode: 'nft',467          pendingSponsor: sponsorCross,468        },469      ).send();470      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);471472      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true;473474      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});475      let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});476      expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true));477      expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false;478479      await collectionEvm.methods.removeCollectionSponsor().send({from: owner});480481      sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner});482      expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000');483    });484485    itEth('Can sponsor from evm address via access list', async ({helper}) => {486      const owner = await helper.eth.createAccountWithBalance(donor);487      const sponsorEth = await helper.eth.createAccountWithBalance(donor);488      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth);489490      const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(491        owner,492        {493          ...CREATE_COLLECTION_DATA_DEFAULTS,494          name: 'Sponsor collection',495          description: '1',496          tokenPrefix: '1',497          collectionMode: 'nft',498          pendingSponsor: sponsorCross,499          limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}],500          tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}],501        },502        '',503      );504505      const collectionSub = helper.nft.getCollectionObject(collectionId);506      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);507508      let sponsorship = (await collectionSub.getData())!.raw.sponsorship;509      expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));510      // Account cannot confirm sponsorship if it is not set as a sponsor511      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');512513      // Sponsor can confirm sponsorship:514      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});515      sponsorship = (await collectionSub.getData())!.raw.sponsorship;516      expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));517518      // Create user with no balance:519      const user = helper.ethCrossAccount.createAccount();520      const nextTokenId = await collectionEvm.methods.nextTokenId().call();521      expect(nextTokenId).to.be.equal('1');522523      // Set collection permissions:524      const oldPermissions = (await collectionSub.getData())!.raw.permissions;525      expect(oldPermissions.mintMode).to.be.false;526      expect(oldPermissions.access).to.be.equal('Normal');527528      await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});529      await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});530      await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});531532      const newPermissions = (await collectionSub.getData())!.raw.permissions;533      expect(newPermissions.mintMode).to.be.true;534      expect(newPermissions.access).to.be.equal('AllowList');535536      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));537      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));538      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));539540      // User can mint token without balance:541      {542        const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});543        const event = helper.eth.normalizeEvents(result.events)544          .find(event => event.event === 'Transfer');545546        expect(event).to.be.deep.equal({547          address: collectionAddress,548          event: 'Transfer',549          args: {550            from: '0x0000000000000000000000000000000000000000',551            to: user.eth,552            tokenId: '1',553          },554        });555556        // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});557558        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));559        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));560        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));561562        expect(await collectionEvm.methods.properties(nextTokenId, []).call())563          .to.be.like([564            [565              'key',566              '0x' + Buffer.from('Value').toString('hex'),567            ],568          ]);569        expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);570        expect(userBalanceAfter).to.be.eq(userBalanceBefore);571        expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;572      }573    });574575    itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => {576      const owner = await helper.eth.createAccountWithBalance(donor);577      const sponsor = await helper.eth.createAccountWithBalance(donor);578      const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);579      const user = helper.eth.createAccount();580      const userCross = helper.ethCrossAccount.fromAddress(user);581582      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(583        owner,584        {585          ...CREATE_COLLECTION_DATA_DEFAULTS,586          name: 'Sponsor collection',587          description: '1',588          tokenPrefix: '1',589          collectionMode: 'nft',590          pendingSponsor: sponsorCross,591          adminList: [userCross],592        },593        '',594      );595596      const collectionSub = helper.nft.getCollectionObject(collectionId);597      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);598      // Set collection sponsor:599      let collectionData = (await collectionSub.getData())!;600      expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));601      await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail');602603      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});604      collectionData = (await collectionSub.getData())!;605      expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true));606607      const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));608      const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));609610      const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});611      const tokenId = mintingResult.events.Transfer.returnValues.tokenId;612613      const event = helper.eth.normalizeEvents(mintingResult.events)614        .find(event => event.event === 'Transfer');615      const address = helper.ethAddress.fromCollectionId(collectionId);616617      expect(event).to.be.deep.equal({618        address,619        event: 'Transfer',620        args: {621          from: '0x0000000000000000000000000000000000000000',622          to: user,623          tokenId: '1',624        },625      });626      expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI');627628      const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));629      expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);630      const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));631      expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;632    });633634    itEth('Can reassign collection sponsor', async ({helper}) => {635      const owner = await helper.eth.createAccountWithBalance(donor);636      const sponsorEth = await helper.eth.createAccountWithBalance(donor);637      const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth);638      const [sponsorSub] = await helper.arrange.createAccounts([100n], donor);639      const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub);640641      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(642        owner,643        {644          ...CREATE_COLLECTION_DATA_DEFAULTS,645          name: 'Sponsor collection',646          description: '1',647          tokenPrefix: '1',648          collectionMode: 'nft',649          pendingSponsor: sponsorCrossEth,650        },651        '',652      );653      const collectionSub = helper.nft.getCollectionObject(collectionId);654      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);655656      // Set and confirm sponsor:657      await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});658659      // Can reassign sponsor:660      await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner});661      const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship;662      expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address});663    });664665    [666      'transfer',667      'transferCross',668      'transferFrom',669      'transferFromCross',670    ].map(testCase =>671      itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => {672        const owner = await helper.eth.createAccountWithBalance(donor);673        const sponsor = await helper.eth.createAccountWithBalance(donor);674        const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor);675        const user = await helper.eth.createAccountWithBalance(donor);676        const userCross = helper.ethCrossAccount.fromAddress(user);677678        const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection(679          owner,680          {681            ...CREATE_COLLECTION_DATA_DEFAULTS,682            name: 'Sponsor collection',683            description: '1',684            tokenPrefix: '1',685            collectionMode: 'rft',686            pendingSponsor: sponsorCross,687            adminList: [userCross],688          },689          '',690        );691        const receiver = await helper.eth.createAccountWithBalance(donor);692        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);693694        await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});695696        const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});697        const tokenId = result.events.Transfer.returnValues.tokenId;698699        const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));700        const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));701        const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));702703        switch (testCase) {704          case 'transfer':705            await collectionEvm.methods.transfer(receiver, tokenId).send({from: user});706            break;707          case 'transferCross':708            await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});709            break;710          case 'transferFrom':711            await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user});712            break;713          case 'transferFromCross':714            await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user});715            break;716        }717718        const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));719        expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);720        const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor));721        expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;722        const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));723        expect(userBalanceAfter).to.be.eq(userBalanceBefore);724      }));725  });726727  describe('Collection admins', () => {728    let donor: IKeyringPair;729730    before(async function() {731      await usingEthPlaygrounds(async (_helper, privateKey) => {732        donor = await privateKey({url: import.meta.url});733      });734    });735736    [737      {mode: 'nft' as const, requiredPallets: []},738      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},739      {mode: 'ft' as const, requiredPallets: []},740    ].map(testCase => {741      itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => {742        // arrange743        const owner = await helper.eth.createAccountWithBalance(donor);744        const adminSub = await privateKey('//admin2');745        const adminEth = helper.eth.createAccount().toLowerCase();746747        const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);748        const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);749750        const {collectionAddress, collectionId} = await helper.eth.createCollection(751          owner,752          {753            ...CREATE_COLLECTION_DATA_DEFAULTS,754            name: 'Mint collection',755            description: 'a',756            tokenPrefix: 'b',757            collectionMode: testCase.mode,758            adminList: [adminCrossSub, adminCrossEth],759          },760        ).send();761        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true);762763        // 1. Expect api.rpc.unique.adminlist returns admins:764        const adminListRpc = await helper.collection.getAdmins(collectionId);765        expect(adminListRpc).to.has.length(2);766        expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]);767768        // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist769        let adminListEth = await collectionEvm.methods.collectionAdmins().call();770        adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element));771        expect(adminListRpc).to.be.like(adminListEth);772773        // 3. check isOwnerOrAdminCross returns true:774        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true;775        expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true;776      });777    });778779    itEth('cross account admin can mint', async ({helper}) => {780      // arrange: create collection and accounts781      const owner = await helper.eth.createAccountWithBalance(donor);782      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();783      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);784      const [adminSub] = await helper.arrange.createAccounts([100n], donor);785      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);786      const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection(787        owner,788        {789          ...CREATE_COLLECTION_DATA_DEFAULTS,790          name: 'Mint collection',791          description: 'a',792          tokenPrefix: 'b',793          collectionMode: 'nft',794          adminList: [adminCrossSub, adminCrossEth],795        },796        'uri',797      );798      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true);799800      // admin (sub and eth) can mint token:801      await collectionEvm.methods.mint(owner).send({from: adminEth});802      await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}});803804      expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2);805    });806807    itEth('cannot add invalid cross account admin', async ({helper}) => {808      const owner = await helper.eth.createAccountWithBalance(donor);809      const [admin] = await helper.arrange.createAccounts([100n, 100n], donor);810811      const adminCross = {812        eth: helper.address.substrateToEth(admin.address),813        sub: admin.addressRaw,814      };815816      await expect(helper.eth.createCollection(817        owner,818        {819          ...CREATE_COLLECTION_DATA_DEFAULTS,820          name: 'A',821          description: 'B',822          tokenPrefix: 'C',823          collectionMode: 'nft',824          adminList: [adminCross],825        },826      ).call()).to.be.rejected;827    });828829    itEth('Remove [cross] admin by owner', async ({helper}) => {830      const owner = await helper.eth.createAccountWithBalance(donor);831832      const [adminSub] = await helper.arrange.createAccounts([10n], donor);833      const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase();834      const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub);835      const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth);836837      const {collectionAddress, collectionId} = await helper.eth.createCollection(838        owner,839        {840          ...CREATE_COLLECTION_DATA_DEFAULTS,841          name: 'A',842          description: 'B',843          tokenPrefix: 'C',844          collectionMode: 'nft',845          adminList: [adminCrossSub, adminCrossEth],846        },847      ).send();848849      const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);850851      {852        const adminList = await helper.collection.getAdmins(collectionId);853        expect(adminList).to.deep.include({Substrate: adminSub.address});854        expect(adminList).to.deep.include({Ethereum: adminEth});855      }856857      await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send();858      await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send();859      const adminList = await helper.collection.getAdmins(collectionId);860      expect(adminList.length).to.be.eq(0);861862      // Non admin cannot mint:863      await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/);864      await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected;865    });866  });867868  describe('Collection limits', () => {869    describe('Can set collection limits', () => {870      [871        {case: 'nft' as const},872        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},873        {case: 'ft' as const},874      ].map(testCase =>875        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {876          const owner = await helper.eth.createAccountWithBalance(donor);877878          const limits = {879            accountTokenOwnershipLimit: 1000n,880            sponsoredDataSize: 1024n,881            sponsoredDataRateLimit: 30n,882            tokenLimit: 1000000n,883            sponsorTransferTimeout: 6n,884            sponsorApproveTimeout: 6n,885            ownerCanTransfer: 1n,886            ownerCanDestroy: 0n,887            transfersEnabled: 0n,888          };889890          const {collectionId, collectionAddress} = await helper.eth.createCollection(891            owner,892            {893              ...CREATE_COLLECTION_DATA_DEFAULTS,894              name: 'Limits',895              description: 'absolutely anything',896              tokenPrefix: 'FLO',897              collectionMode: testCase.case,898              limits: [899                {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit},900                {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize},901                {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit},902                {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit},903                {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout},904                {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout},905                {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer},906                {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy},907                {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled},908              ],909            },910          ).send();911912          const expectedLimits = {913            accountTokenOwnershipLimit: 1000,914            sponsoredDataSize: 1024,915            sponsoredDataRateLimit: {blocks: 30},916            tokenLimit: 1000000,917            sponsorTransferTimeout: 6,918            sponsorApproveTimeout: 6,919            ownerCanTransfer: true,920            ownerCanDestroy: false,921            transfersEnabled: false,922          };923924          const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner);925926          // Check limits from sub:927          const data = (await helper.rft.getData(collectionId))!;928          expect(data.raw.limits).to.deep.eq(expectedLimits);929          expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits);930          // Check limits from eth:931          const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner});932          expect(limitsEvm).to.have.length(9);933          expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]);934          expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]);935          expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]);936          expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]);937          expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]);938          expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]);939          expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]);940          expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]);941          expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]);942        }));943    });944945    describe('(!negative test!) Cannot set invalid collection limits', () => {946      [947        {case: 'nft' as const},948        {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]},949        {case: 'ft' as const},950      ].map(testCase =>951        itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => {952          const invalidLimits = {953            accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER),954            transfersEnabled: 3,955          };956957          const createCollectionData = {958            ...CREATE_COLLECTION_DATA_DEFAULTS,959            name: 'Limits',960            description: 'absolutely anything',961            tokenPrefix: 'ISNI',962            collectionMode: testCase.case,963          };964965          const owner = await helper.eth.createAccountWithBalance(donor);966          await expect(helper.eth.createCollection(967            owner,968            {969              ...createCollectionData,970              limits: [{field: 9 as CollectionLimitField, value: 1n}],971            },972          ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"');973974          await expect(helper.eth.createCollection(975            owner,976            {977              ...createCollectionData,978              limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}],979            },980          ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`);981982          await expect(helper.eth.createCollection(983            owner,984            {985              ...createCollectionData,986              limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}],987            },988          ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`);989990          await expect(helper.eth.createCollection(991            owner,992            {993              ...createCollectionData,994              limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}],995            },996          ).call()).to.be.rejectedWith('value out-of-bounds');997        }));998    });999  });10001001  describe('Collection properties', () => {10021003    [1004      {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'}]},1005      {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'}]},1006      {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'}]},1007    ].map(testCase =>1008      itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1009        const caller = await helper.eth.createAccountWithBalance(donor);1010        const callerCross = helper.ethCrossAccount.fromAddress(caller);1011        const owner = await helper.eth.createAccountWithBalance(donor);1012        const {collectionId, collectionAddress} = await helper.eth.createCollection(1013          owner,1014          {1015            ...CREATE_COLLECTION_DATA_DEFAULTS,1016            name: 'name',1017            description: 'test',1018            tokenPrefix: 'test',1019            collectionMode: testCase.mode,1020            adminList: [callerCross],1021            properties: testCase.methodParams,1022          },1023        ).send();10241025        const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10261027        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;1028        expect(raw.properties).to.deep.equal(testCase.expectedProps);10291030        // collectionProperties returns properties:1031        expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value)));1032      }));10331034    [1035      {mode: 'nft' as const},1036      {mode: 'rft' as const},1037      {mode: 'ft' as const},1038    ].map(testCase =>1039      itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => {1040        const caller = await helper.eth.createAccountWithBalance(donor);1041        const callerCross = helper.ethCrossAccount.fromAddress(caller);1042        const owner = await helper.eth.createAccountWithBalance(donor);1043        const {collectionId, collectionAddress} = await helper.eth.createCollection(1044          owner,1045          {1046            ...CREATE_COLLECTION_DATA_DEFAULTS,1047            name: 'name',1048            description: 'test',1049            tokenPrefix: 'test',1050            collectionMode: testCase.mode,1051            adminList: [callerCross],1052            properties:[1053              {key: 'testKey1', value: Buffer.from('testValue1')},1054              {key: 'testKey2', value: Buffer.from('testValue2')},1055              {key: 'testKey3', value: Buffer.from('testValue3')}],1056          },1057        ).send();10581059        const collectionEvm = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);10601061        await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller});10621063        const raw = (await helper[testCase.mode].getData(collectionId))?.raw;10641065        expect(raw.properties.length).to.equal(1);1066        expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]);1067      }));10681069    itEth('(!negative test!) Cannot set invalid properties', async({helper}) => {1070      const caller = await helper.eth.createAccountWithBalance(donor);1071      const callerCross = helper.ethCrossAccount.fromAddress(caller);1072      const owner = await helper.eth.createAccountWithBalance(donor);1073      const createCollectionData = {1074        ...CREATE_COLLECTION_DATA_DEFAULTS,1075        name: 'name',1076        description: 'test',1077        tokenPrefix: 'test',1078        collectionMode: 'nft' as TCollectionMode,1079        adminList: [callerCross],1080      };1081      await expect(helper.eth.createCollection(1082        owner,1083        {1084          ...createCollectionData,1085          properties: [{key: '', value: Buffer.from('val1')}],1086        },1087      ).call()).to.be.rejected;10881089      await expect(helper.eth.createCollection(1090        owner,1091        {1092          ...createCollectionData,1093          properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}],1094        },1095      ).call()).to.be.rejected;10961097      await expect(helper.eth.createCollection(1098        owner,1099        {1100          ...createCollectionData,1101          properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}],1102        },1103      ).call()).to.be.rejected;1104    });11051106    itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => {1107      const caller = await helper.eth.createAccountWithBalance(donor);1108      const owner = await helper.eth.createAccountWithBalance(donor);1109      const {collectionAddress} = await helper.eth.createCollection(1110        owner,1111        {1112          ...CREATE_COLLECTION_DATA_DEFAULTS,1113          name: 'name',1114          description: 'test',1115          tokenPrefix: 'test',1116          collectionMode: 'nft',1117          properties:[1118            {key: 'testKey1', value: Buffer.from('testValue1')},1119            {key: 'testKey2', value: Buffer.from('testValue2')}],1120        },1121      ).send();11221123      const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);11241125      await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected;1126    });1127  });11281129  describe('Token property permissions', () => {1130    [1131      {mode: 'nft' as const, requiredPallets: []},1132      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1133    ].map(testCase =>1134      itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => {1135        const owner = await helper.eth.createAccountWithBalance(donor);1136        const caller = await helper.ethCrossAccount.createAccountWithBalance(donor);1137        for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {1138          const {collectionId, collectionAddress} = await helper.eth.createCollection(1139            owner,1140            {1141              ...CREATE_COLLECTION_DATA_DEFAULTS,1142              name: 'A',1143              description: 'B',1144              tokenPrefix: 'C',1145              collectionMode: testCase.mode,1146              adminList: [caller],1147              tokenPropertyPermissions: [1148                {1149                  key: 'testKey',1150                  permissions: [1151                    {code: TokenPermissionField.Mutable, value: mutable},1152                    {code: TokenPermissionField.TokenOwner, value: tokenOwner},1153                    {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin},1154                  ],1155                },1156              ],1157            },1158          ).send();1159          const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);11601161          expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{1162            key: 'testKey',1163            permission: {mutable, collectionAdmin, tokenOwner},1164          }]);11651166          expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([1167            ['testKey', [1168              [TokenPermissionField.Mutable.toString(), mutable],1169              [TokenPermissionField.TokenOwner.toString(), tokenOwner],1170              [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]],1171            ],1172          ]);1173        }1174      }));11751176    [1177      {mode: 'nft' as const, requiredPallets: []},1178      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1179    ].map(testCase =>1180      itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => {1181        const owner = await helper.eth.createAccountWithBalance(donor);1182        const {collectionId, collectionAddress} = await helper.eth.createCollection(1183          owner,1184          {1185            ...CREATE_COLLECTION_DATA_DEFAULTS,1186            name: 'A',1187            description: 'B',1188            tokenPrefix: 'C',1189            collectionMode: testCase.mode,1190            tokenPropertyPermissions: [1191              {1192                key: 'testKey_0',1193                permissions: [1194                  {code: TokenPermissionField.Mutable, value: true},1195                  {code: TokenPermissionField.TokenOwner, value: true},1196                  {code: TokenPermissionField.CollectionAdmin, value: true}],1197              },1198              {1199                key: 'testKey_1',1200                permissions: [1201                  {code: TokenPermissionField.Mutable, value: true},1202                  {code: TokenPermissionField.TokenOwner, value: false},1203                  {code: TokenPermissionField.CollectionAdmin, value: true}],1204              },1205              {1206                key: 'testKey_2',1207                permissions: [1208                  {code: TokenPermissionField.Mutable, value: false},1209                  {code: TokenPermissionField.TokenOwner, value: true},1210                  {code: TokenPermissionField.CollectionAdmin, value: false}],1211              },1212            ],1213          },1214        ).send();1215        const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);12161217        expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([1218          {1219            key: 'testKey_0',1220            permission: {mutable: true, tokenOwner: true, collectionAdmin: true},1221          },1222          {1223            key: 'testKey_1',1224            permission: {mutable: true, tokenOwner: false, collectionAdmin: true},1225          },1226          {1227            key: 'testKey_2',1228            permission: {mutable: false, tokenOwner: true, collectionAdmin: false},1229          },1230        ]);12311232        expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([1233          ['testKey_0', [1234            [TokenPermissionField.Mutable.toString(), true],1235            [TokenPermissionField.TokenOwner.toString(), true],1236            [TokenPermissionField.CollectionAdmin.toString(), true]],1237          ],1238          ['testKey_1', [1239            [TokenPermissionField.Mutable.toString(), true],1240            [TokenPermissionField.TokenOwner.toString(), false],1241            [TokenPermissionField.CollectionAdmin.toString(), true]],1242          ],1243          ['testKey_2', [1244            [TokenPermissionField.Mutable.toString(), false],1245            [TokenPermissionField.TokenOwner.toString(), true],1246            [TokenPermissionField.CollectionAdmin.toString(), false]],1247          ],1248        ]);1249      }));12501251    [1252      {mode: 'nft' as const, requiredPallets: []},1253      {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},1254    ].map(testCase =>1255      itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {1256        const caller = await helper.eth.createAccountWithBalance(donor);1257        const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);1258        const {collectionAddress} = await helper.eth.createCollection(1259          caller,1260          {1261            ...CREATE_COLLECTION_DATA_DEFAULTS,1262            name: 'A',1263            description: 'B',1264            tokenPrefix: 'C',1265            collectionMode: testCase.mode,1266            adminList: [receiver],1267            tokenPropertyPermissions: [1268              {1269                key: 'testKey',1270                permissions: [1271                  {code: TokenPermissionField.Mutable, value: true},1272                  {code: TokenPermissionField.CollectionAdmin, value: true}],1273              },1274              {1275                key: 'testKey_1',1276                permissions: [1277                  {code: TokenPermissionField.Mutable, value: true},1278                  {code: TokenPermissionField.CollectionAdmin, value: true}],1279              },1280            ],1281          },1282        ).send();12831284        const collection = helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller);1285        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;1286        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2);12871288        await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller});1289        expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0);1290      }));1291  });12921293  describe('Nesting', () => {1294    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {1295      const owner = await helper.eth.createAccountWithBalance(donor);1296      const {collectionAddress, collectionId} = await helper.eth.createCollection(1297        owner,1298        {1299          ...CREATE_COLLECTION_DATA_DEFAULTS,1300          name: 'A',1301          description: 'B',1302          tokenPrefix: 'C',1303          collectionMode: 'nft',1304          nestingSettings: {token_owner: true, collection_admin: false, restricted: []},1305        },1306      ).send();13071308      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13091310      // Create a token to be nested to1311      const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});1312      const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;1313      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);13141315      // Create a nested token1316      const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});1317      const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;1318      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);13191320      // Create a token to be nested and nest1321      const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});1322      const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;13231324      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});1325      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);13261327      // Unnest token back1328      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});1329      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);1330    });13311332    itEth('NFT: collectionNesting()', async ({helper}) => {1333      const owner = await helper.eth.createAccountWithBalance(donor);1334      const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection(1335        owner,1336        new CreateCollectionData('A', 'B', 'C', 'nft'),1337      ).send();13381339      const unnestedContract = helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);1340      expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);13411342      const {collectionAddress} = await helper.eth.createCollection(1343        owner,1344        {1345          ...CREATE_COLLECTION_DATA_DEFAULTS,1346          name: 'A',1347          description: 'B',1348          tokenPrefix: 'C',1349          collectionMode: 'nft',1350          nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]},1351        },1352      ).send();13531354      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);1355      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]);1356      await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});1357      expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);1358    });13591360    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {1361      const owner = await helper.eth.createAccountWithBalance(donor);13621363      const {collectionId, collectionAddress} = await helper.eth.createCollection(1364        owner,1365        {1366          ...CREATE_COLLECTION_DATA_DEFAULTS,1367          name: 'A',1368          description: 'B',1369          tokenPrefix: 'C',1370          collectionMode: 'nft',1371          nestingSettings: {token_owner: false, collection_admin: false, restricted: []},1372        },1373      ).send();13741375      const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13761377      // Create a token to nest into1378      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});1379      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;1380      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);13811382      // Create a token to nest1383      const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});1384      const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;13851386      // Try to nest1387      await expect(contract.methods1388        .transfer(targetNftTokenAddress, nftTokenId)1389        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');1390    });1391  });13921393  describe('Flags', () => {1394    const createCollectionData = {1395      ...CREATE_COLLECTION_DATA_DEFAULTS,1396      name: 'A',1397      description: 'B',1398      tokenPrefix: 'C',1399      collectionMode: 'nft' as TCollectionMode,1400    };14011402    itEth('NFT: use numbers for flags', async ({helper}) => {1403      const owner = await helper.eth.createAccountWithBalance(donor);14041405      {1406        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send();1407        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false});1408      }14091410      {1411        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send();1412        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1413      }1414    });14151416    itEth('NFT: can\'t set foreign flag number', async ({helper}) => {1417      const owner = await helper.eth.createAccountWithBalance(donor);14181419      {1420        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1421      }14221423      {1424        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1425      }1426    });14271428    itEth('NFT: use enum for flags', async ({helper}) => {1429      const owner = await helper.eth.createAccountWithBalance(donor);14301431      {1432        const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send();1433        expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true});1434      }1435    });14361437    itEth('NFT: foreign flag enum is ignored', async ({helper}) => {1438      const owner = await helper.eth.createAccountWithBalance(donor);14391440      {1441        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1442      }14431444      {1445        await expect(helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).call({from: owner})).to.be.rejectedWith(/internal flags were used/);1446      }1447    });1448  });1449});