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

difftreelog

Mint tests generalization

Max Andreev2023-01-13parent: #8a76dd7.patch.diff
in: master

8 files changed

deletedtests/src/eth/collections/callMethodsERC20.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collections/callMethodsERC20.test.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {Pallets, requirePalletsOrSkip} from '../../util';
-import {expect, itEth, usingEthPlaygrounds} from '../util';
-import {IKeyringPair} from '@polkadot/types/types';
-
-[
-  {mode: 'ft' as const, requiredPallets: []},
-  {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
-  describe(`${testCase.mode.toUpperCase()}: ERC-20 call methods`, () => {
-    let donor: IKeyringPair;
-
-    before(async function() {
-      await usingEthPlaygrounds(async (helper, privateKey) => {
-        requirePalletsOrSkip(this, helper, testCase.requiredPallets);
-        donor = await privateKey({filename: __filename});
-      });
-    });
-
-    itEth('totalSupply', async ({helper}) => {
-      const caller = await helper.eth.createAccountWithBalance(donor);
-      const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller];
-
-      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6');
-      if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
-
-      // Use collection contract for FT or token contract for RFT:
-      const contract = testCase.mode === 'ft'
-        ? collection
-        : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
-
-      // Mint tokens:
-      testCase.mode === 'ft'
-        ? await contract.methods.mint(...mintingParams).send({from: caller})
-        : await contract.methods.repartition(200).send({from: caller});
-
-      const totalSupply = await contract.methods.totalSupply().call();
-      expect(totalSupply).to.equal('200');
-    });
-
-    itEth('balanceOf', async ({helper}) => {
-      const caller = await helper.eth.createAccountWithBalance(donor);
-      const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller];
-
-      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
-      if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
-
-      // Use collection contract for FT or token contract for RFT:
-      const contract = testCase.mode === 'ft'
-        ? collection
-        : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
-
-      // Mint tokens:
-      testCase.mode === 'ft'
-        ? await contract.methods.mint(...mintingParams).send({from: caller})
-        : await contract.methods.repartition(200).send({from: caller});
-
-      const balance = await contract.methods.balanceOf(caller).call();
-      expect(balance).to.equal('200');
-    });
-
-    itEth('decimals', async ({helper}) => {
-      const caller = await helper.eth.createAccountWithBalance(donor);
-      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
-      if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
-
-      // Use collection contract for FT or token contract for RFT:
-      const contract = testCase.mode === 'ft'
-        ? collection
-        : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
-
-      const decimals = await contract.methods.decimals().call();
-      expect(decimals).to.equal(testCase.mode === 'rft' ? '0' : '18');
-    });
-  });
-});
\ No newline at end of file
deletedtests/src/eth/collections/callMethodsERC721.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collections/callMethodsERC721.test.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {Pallets, requirePalletsOrSkip} from '../../util';
-import {expect, itEth, usingEthPlaygrounds} from '../util';
-import {IKeyringPair} from '@polkadot/types/types';
-
-[
-  {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
-  {mode: 'nft' as const, requiredPallets: []},
-].map(testCase => {
-  describe(`${testCase.mode.toUpperCase()}: ERC-721 call methods`, () => {
-    let donor: IKeyringPair;
-
-    before(async function() {
-      await usingEthPlaygrounds(async (helper, privateKey) => {
-        requirePalletsOrSkip(this, helper, testCase.requiredPallets);
-
-        donor = await privateKey({filename: __filename});
-      });
-    });
-
-    itEth('name/symbol/description', async ({helper}) => {
-      const callerEth = await helper.eth.createAccountWithBalance(donor);
-      const [callerSub] = await helper.arrange.createAccounts([100n], donor);
-      const [name, description, tokenPrefix] = ['Name', 'Description', 'Symbol'];
-
-      const {collection: collectionEth} = await helper.eth.createCollection(testCase.mode, callerEth, name, description, tokenPrefix);
-      await collectionEth.methods.mint(callerEth).send({from: callerEth});
-      const {collectionId} = await helper[testCase.mode].mintCollection(callerSub, {name, description, tokenPrefix});
-      const collectionSub = await helper.ethNativeContract.collectionById(collectionId, testCase.mode, callerEth);
-
-      // Can get name/symbol/description for Eth collection
-      expect(await collectionEth.methods.name().call()).to.eq(name);
-      expect(await collectionEth.methods.symbol().call()).to.eq(tokenPrefix);
-      expect(await collectionEth.methods.description().call()).to.eq(description);
-      // Can get name/symbol/description for Sub collection
-      expect(await collectionSub.methods.name().call()).to.eq(name);
-      expect(await collectionSub.methods.symbol().call()).to.eq(tokenPrefix);
-      expect(await collectionSub.methods.description().call()).to.eq(description);
-    });
-
-    itEth('totalSupply', async ({helper}) => {
-      const caller = await helper.eth.createAccountWithBalance(donor);
-
-      const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6');
-      await collection.methods.mint(caller).send({from: caller});
-
-      const totalSupply = await collection.methods.totalSupply().call();
-      expect(totalSupply).to.equal('1');
-    });
-
-    itEth('balanceOf', async ({helper}) => {
-      const caller = await helper.eth.createAccountWithBalance(donor);
-
-      const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
-      await collection.methods.mint(caller).send({from: caller});
-      await collection.methods.mint(caller).send({from: caller});
-      await collection.methods.mint(caller).send({from: caller});
-
-      const balance = await collection.methods.balanceOf(caller).call();
-      expect(balance).to.equal('3');
-    });
-
-    itEth('ownerOf', async ({helper}) => {
-      const caller = await helper.eth.createAccountWithBalance(donor);
-      const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf', '6', '6');
-
-      const result = await collection.methods.mint(caller).send();
-      const tokenId = result.events.Transfer.returnValues.tokenId;
-
-      const owner = await collection.methods.ownerOf(tokenId).call();
-      expect(owner).to.equal(caller);
-    });
-
-    // TODO move to rft tests:
-    // itEth('ownerOf after burn', async ({helper}) => {
-    //   const caller = await helper.eth.createAccountWithBalance(donor);
-    //   const receiver = helper.eth.createAccount();
-    //   const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');
-    //   const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
-
-    //   const result = await contract.methods.mint(caller).send();
-    //   const tokenId = result.events.Transfer.returnValues.tokenId;
-    //   const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);
-
-    //   await tokenContract.methods.repartition(2).send();
-    //   await tokenContract.methods.transfer(receiver, 1).send();
-
-    //   await tokenContract.methods.burnFrom(caller, 1).send();
-
-    //   const owner = await contract.methods.ownerOf(tokenId).call();
-    //   expect(owner).to.equal(receiver);
-    // });
-
-    // TODO move to rft tests:
-    // itEth('ownerOf for partial ownership', async ({helper}) => {
-    //   const caller = await helper.eth.createAccountWithBalance(donor);
-    //   const receiver = helper.eth.createAccount();
-    //   const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');
-    //   const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
-
-    //   const result = await contract.methods.mint(caller).send();
-    //   const tokenId = result.events.Transfer.returnValues.tokenId;
-    //   const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);
-
-    //   await tokenContract.methods.repartition(2).send();
-    //   await tokenContract.methods.transfer(receiver, 1).send();
-
-    //   const owner = await contract.methods.ownerOf(tokenId).call();
-    //   expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');
-    // });
-  });
-});
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -29,24 +29,6 @@
     });
   });
 
-  itEth('Can perform mint()', async ({helper}) => {
-    const owner = await helper.eth.createAccountWithBalance(donor);
-    const receiver = helper.eth.createAccount();
-    const collection = await helper.ft.mintCollection(alice);
-    await collection.addAdmin(alice, {Ethereum: owner});
-
-    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
-    const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
-
-    const result = await contract.methods.mint(receiver, 100).send();
-
-    const event = result.events.Transfer;
-    expect(event.address).to.equal(collectionAddress);
-    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
-    expect(event.returnValues.to).to.equal(receiver);
-    expect(event.returnValues.value).to.equal('100');
-  });
-
   [
     'substrate' as const,
     'ethereum' as const,
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
before · tests/src/eth/nonFungible.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// 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 {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';20import {ITokenPropertyPermission} from '../util/playgrounds/types';2122describe('Check ERC721 token URI for NFT', () => {23  let donor: IKeyringPair;2425  before(async function() {26    await usingEthPlaygrounds(async (_helper, privateKey) => {27      donor = await privateKey({filename: __filename});28    });29  });3031  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {32    const owner = await helper.eth.createAccountWithBalance(donor);33    const receiver = helper.eth.createAccount();3435    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);36    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);3738    const result = await contract.methods.mint(receiver).send();39    const tokenId = result.events.Transfer.returnValues.tokenId;40    expect(tokenId).to.be.equal('1');4142    if (propertyKey && propertyValue) {43      // Set URL or suffix44      await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();45    }4647    const event = result.events.Transfer;48    expect(event.address).to.be.equal(collectionAddress);49    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');50    expect(event.returnValues.to).to.be.equal(receiver);51    expect(event.returnValues.tokenId).to.be.equal(tokenId);5253    return {contract, nextTokenId: tokenId};54  }5556  itEth('Empty tokenURI', async ({helper}) => {57    const {contract, nextTokenId} = await setup(helper, '');58    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');59  });6061  itEth('TokenURI from url', async ({helper}) => {62    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');63    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');64  });6566  itEth('TokenURI from baseURI', async ({helper}) => {67    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');68    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');69  });7071  itEth('TokenURI from baseURI + suffix', async ({helper}) => {72    const suffix = '/some/suffix';73    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);74    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);75  });76});7778describe('NFT: Plain calls', () => {79  let donor: IKeyringPair;80  let minter: IKeyringPair;81  let bob: IKeyringPair;82  let charlie: IKeyringPair;8384  before(async function() {85    await usingEthPlaygrounds(async (helper, privateKey) => {86      donor = await privateKey({filename: __filename});87      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);88    });89  });9091  itEth('Can perform mint() & get crossOwner()', async ({helper}) => {92    const owner = await helper.eth.createAccountWithBalance(donor);93    const receiver = helper.eth.createAccount();9495    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');96    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9798    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();99    const tokenId = result.events.Transfer.returnValues.tokenId;100    expect(tokenId).to.be.equal('1');101102    const event = result.events.Transfer;103    expect(event.address).to.be.equal(collectionAddress);104    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');105    expect(event.returnValues.to).to.be.equal(receiver);106107    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');108    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);109    // TODO: this wont work right now, need release 919000 first110    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();111    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();112    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);113  });114115  // TODO combine all minting tests in one place116  [117    'substrate' as const,118    'ethereum' as const,119  ].map(testCase => {120    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {121      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);122123      const receiverEth = helper.eth.createAccount();124      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);125      const receiverSub = bob;126      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);127128      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);129      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });130      const permissions: ITokenPropertyPermission[] = properties131        .map(p => {132          return {133            key: p.key, permission: {134              tokenOwner: false,135              collectionAdmin: true,136              mutable: false,137            },138          };139        });140141      const collection = await helper.nft.mintCollection(minter, {142        tokenPrefix: 'ethp',143        tokenPropertyPermissions: permissions,144      });145      await collection.addAdmin(minter, {Ethereum: collectionAdmin});146147      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);148      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);149      let expectedTokenId = await contract.methods.nextTokenId().call();150      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();151      let tokenId = result.events.Transfer.returnValues.tokenId;152      expect(tokenId).to.be.equal(expectedTokenId);153154      let event = result.events.Transfer;155      expect(event.address).to.be.equal(collectionAddress);156      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');157      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));158      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);159160      expectedTokenId = await contract.methods.nextTokenId().call();161      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();162      event = result.events.Transfer;163      expect(event.address).to.be.equal(collectionAddress);164      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');165      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));166      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);167168      tokenId = result.events.Transfer.returnValues.tokenId;169170      expect(tokenId).to.be.equal(expectedTokenId);171172      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties173        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));174175      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))176        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});177    });178  });179180  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {181    const nonOwner = await helper.eth.createAccountWithBalance(donor);182    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);183184    const collection = await helper.nft.mintCollection(minter);185    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);186    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');187188    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))189      .to.be.rejectedWith('PublicMintingNotAllowed');190  });191192  //TODO: CORE-302 add eth methods193  itEth.skip('Can perform mintBulk()', async ({helper}) => {194    const caller = await helper.eth.createAccountWithBalance(donor);195    const receiver = helper.eth.createAccount();196197    const collection = await helper.nft.mintCollection(minter);198    await collection.addAdmin(minter, {Ethereum: caller});199200    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);201    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);202    {203      const bulkSize = 3;204      const nextTokenId = await contract.methods.nextTokenId().call();205      expect(nextTokenId).to.be.equal('1');206      const result = await contract.methods.mintBulkWithTokenURI(207        receiver,208        Array.from({length: bulkSize}, (_, i) => (209          [+nextTokenId + i, `Test URI ${i}`]210        )),211      ).send({from: caller});212213      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);214      for (let i = 0; i < bulkSize; i++) {215        const event = events[i];216        expect(event.address).to.equal(collectionAddress);217        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');218        expect(event.returnValues.to).to.equal(receiver);219        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);220221        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);222      }223    }224  });225226  itEth('Can perform burn()', async ({helper}) => {227    const caller = await helper.eth.createAccountWithBalance(donor);228229    const collection = await helper.nft.mintCollection(minter, {});230    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});231232    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);233    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);234235    {236      const result = await contract.methods.burn(tokenId).send({from: caller});237238      const event = result.events.Transfer;239      expect(event.address).to.be.equal(collectionAddress);240      expect(event.returnValues.from).to.be.equal(caller);241      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');242      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);243    }244  });245246  itEth('Can perform approve()', async ({helper}) => {247    const owner = await helper.eth.createAccountWithBalance(donor);248    const spender = helper.eth.createAccount();249250    const collection = await helper.nft.mintCollection(minter, {});251    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});252253    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);254    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);255256    {257      const result = await contract.methods.approve(spender, tokenId).send({from: owner});258259      const event = result.events.Approval;260      expect(event.address).to.be.equal(collectionAddress);261      expect(event.returnValues.owner).to.be.equal(owner);262      expect(event.returnValues.approved).to.be.equal(spender);263      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);264    }265  });266267  itEth('Can perform setApprovalForAll()', async ({helper}) => {268    const owner = await helper.eth.createAccountWithBalance(donor);269    const operator = helper.eth.createAccount();270271    const collection = await helper.nft.mintCollection(minter, {});272273    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);274    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);275276    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();277    expect(approvedBefore).to.be.equal(false);278279    {280      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});281282      expect(result.events.ApprovalForAll).to.be.like({283        address: collectionAddress,284        event: 'ApprovalForAll',285        returnValues: {286          owner,287          operator,288          approved: true,289        },290      });291292      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();293      expect(approvedAfter).to.be.equal(true);294    }295296    {297      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});298299      expect(result.events.ApprovalForAll).to.be.like({300        address: collectionAddress,301        event: 'ApprovalForAll',302        returnValues: {303          owner,304          operator,305          approved: false,306        },307      });308309      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();310      expect(approvedAfter).to.be.equal(false);311    }312  });313314  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {315    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});316317    const owner = await helper.eth.createAccountWithBalance(donor);318    const operator = await helper.eth.createAccountWithBalance(donor, 100n);319320    const token = await collection.mintToken(minter, {Ethereum: owner});321322    const address = helper.ethAddress.fromCollectionId(collection.collectionId);323    const contract = await helper.ethNativeContract.collection(address, 'nft');324325    {326      await contract.methods.setApprovalForAll(operator, true).send({from: owner});327      const ownerCross = helper.ethCrossAccount.fromAddress(owner);328      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});329      const events = result.events.Transfer;330331      expect(events).to.be.like({332        address,333        event: 'Transfer',334        returnValues: {335          from: owner,336          to: '0x0000000000000000000000000000000000000000',337          tokenId: token.tokenId.toString(),338        },339      });340    }341342    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;343  });344345  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {346    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});347348    const owner = await helper.eth.createAccountWithBalance(donor);349    const operator = await helper.eth.createAccountWithBalance(donor);350    const receiver = charlie;351352    const token = await collection.mintToken(minter, {Ethereum: owner});353354    const address = helper.ethAddress.fromCollectionId(collection.collectionId);355    const contract = await helper.ethNativeContract.collection(address, 'nft');356357    {358      await contract.methods.setApprovalForAll(operator, true).send({from: owner});359      const ownerCross = helper.ethCrossAccount.fromAddress(owner);360      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);361      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});362      const event = result.events.Transfer;363      expect(event).to.be.like({364        address: helper.ethAddress.fromCollectionId(collection.collectionId),365        event: 'Transfer',366        returnValues: {367          from: owner,368          to: helper.address.substrateToEth(receiver.address),369          tokenId: token.tokenId.toString(),370        },371      });372    }373374    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});375  });376377  itEth('Can perform burnFromCross()', async ({helper}) => {378    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});379    const ownerSub = bob;380    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);381    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);382    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);383384    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);385    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);386387    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});388    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});389390    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);391    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');392393    // Approve tokens from substrate and ethereum:394    await token1.approve(ownerSub, {Ethereum: burnerEth});395    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});396397    // can burnFromCross:398    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});399    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});400    const events1 = result1.events.Transfer;401    const events2 = result2.events.Transfer;402403    // Check events for burnFromCross (substrate and ethereum):404    [405      [events1, token1, helper.address.substrateToEth(ownerSub.address)],406      [events2, token2, ownerEth],407    ].map(burnData => {408      expect(burnData[0]).to.be.like({409        address: collectionAddress,410        event: 'Transfer',411        returnValues: {412          from: burnData[2],413          to: '0x0000000000000000000000000000000000000000',414          tokenId: burnData[1].tokenId.toString(),415        },416      });417    });418419    expect(await token1.doesExist()).to.be.false;420    expect(await token2.doesExist()).to.be.false;421  });422423  // TODO combine all approve tests in one place424  itEth('Can perform approveCross()', async ({helper}) => {425    // arrange: create accounts426    const owner = await helper.eth.createAccountWithBalance(donor, 100n);427    const ownerCross = helper.ethCrossAccount.fromAddress(owner);428    const receiverSub = charlie;429    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);430    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);431    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);432433    // arrange: create collection and tokens:434    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});435    const token1 = await collection.mintToken(minter, {Ethereum: owner});436    const token2 = await collection.mintToken(minter, {Ethereum: owner});437438    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');439440    // Can approveCross substrate and ethereum address:441    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});442    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});443    const eventSub = resultSub.events.Approval;444    const eventEth = resultEth.events.Approval;445    expect(eventSub).to.be.like({446      address: helper.ethAddress.fromCollectionId(collection.collectionId),447      event: 'Approval',448      returnValues: {449        owner,450        approved: helper.address.substrateToEth(receiverSub.address),451        tokenId: token1.tokenId.toString(),452      },453    });454    expect(eventEth).to.be.like({455      address: helper.ethAddress.fromCollectionId(collection.collectionId),456      event: 'Approval',457      returnValues: {458        owner,459        approved: receiverEth,460        tokenId: token2.tokenId.toString(),461      },462    });463464    // Substrate address can transferFrom approved tokens:465    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});466    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});467    // Ethereum address can transferFromCross approved tokens:468    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});469    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});470  });471472  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {473    const nonOwner = await helper.eth.createAccountWithBalance(donor);474    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);475    const owner = await helper.eth.createAccountWithBalance(donor);476    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});477    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');478    const token = await collection.mintToken(minter, {Ethereum: owner});479480    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');481  });482483  itEth('Can reaffirm approved address', async ({helper}) => {484    const owner = await helper.eth.createAccountWithBalance(donor, 100n);485    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);486    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);487    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);488    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);489    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});490    const token1 = await collection.mintToken(minter, {Ethereum: owner});491    const token2 = await collection.mintToken(minter, {Ethereum: owner});492    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');493494    // Can approve and reaffirm approved address:495    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});496    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});497498    // receiver1 cannot transferFrom:499    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;500    // receiver2 can transferFrom:501    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});502503    // can set approved address to self address to remove approval:504    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});505    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});506507    // receiver1 cannot transfer token anymore:508    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;509  });510511  itEth('Can perform transferFrom()', async ({helper}) => {512    const owner = await helper.eth.createAccountWithBalance(donor);513    const spender = await helper.eth.createAccountWithBalance(donor);514    const receiver = helper.eth.createAccount();515516    const collection = await helper.nft.mintCollection(minter, {});517    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});518519    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);520    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);521522    await contract.methods.approve(spender, tokenId).send({from: owner});523524    {525      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});526527      const event = result.events.Transfer;528      expect(event.address).to.be.equal(collectionAddress);529      expect(event.returnValues.from).to.be.equal(owner);530      expect(event.returnValues.to).to.be.equal(receiver);531      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);532    }533534    {535      const balance = await contract.methods.balanceOf(receiver).call();536      expect(+balance).to.equal(1);537    }538539    {540      const balance = await contract.methods.balanceOf(owner).call();541      expect(+balance).to.equal(0);542    }543  });544545  itEth('Can perform transferFromCross()', async ({helper}) => {546    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});547548    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);549    const spender = await helper.eth.createAccountWithBalance(donor);550551    const token = await collection.mintToken(minter, {Substrate: owner.address});552553    const address = helper.ethAddress.fromCollectionId(collection.collectionId);554    const contract = await helper.ethNativeContract.collection(address, 'nft');555556    await token.approve(owner, {Ethereum: spender});557558    {559      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);560      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);561      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});562      const event = result.events.Transfer;563      expect(event).to.be.like({564        address: helper.ethAddress.fromCollectionId(collection.collectionId),565        event: 'Transfer',566        returnValues: {567          from: helper.address.substrateToEth(owner.address),568          to: helper.address.substrateToEth(receiver.address),569          tokenId: token.tokenId.toString(),570        },571      });572    }573574    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});575  });576577  itEth('Can perform transfer()', async ({helper}) => {578    const collection = await helper.nft.mintCollection(minter, {});579    const owner = await helper.eth.createAccountWithBalance(donor);580    const receiver = helper.eth.createAccount();581582    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});583584    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);585    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);586587    {588      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});589590      const event = result.events.Transfer;591      expect(event.address).to.be.equal(collectionAddress);592      expect(event.returnValues.from).to.be.equal(owner);593      expect(event.returnValues.to).to.be.equal(receiver);594      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);595    }596597    {598      const balance = await contract.methods.balanceOf(owner).call();599      expect(+balance).to.equal(0);600    }601602    {603      const balance = await contract.methods.balanceOf(receiver).call();604      expect(+balance).to.equal(1);605    }606  });607608  itEth('Can perform transferCross()', async ({helper}) => {609    const collection = await helper.nft.mintCollection(minter, {});610    const owner = await helper.eth.createAccountWithBalance(donor);611    const receiverEth = await helper.eth.createAccountWithBalance(donor);612    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);613    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);614615    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});616617    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);618    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);619620    {621      // Can transferCross to ethereum address:622      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});623      // Check events:624      const event = result.events.Transfer;625      expect(event.address).to.be.equal(collectionAddress);626      expect(event.returnValues.from).to.be.equal(owner);627      expect(event.returnValues.to).to.be.equal(receiverEth);628      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);629630      // owner has balance = 0:631      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();632      expect(+ownerBalance).to.equal(0);633      // receiver owns token:634      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();635      expect(+receiverBalance).to.equal(1);636      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});637    }638639    {640      // Can transferCross to substrate address:641      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});642      // Check events:643      const event = substrateResult.events.Transfer;644      expect(event.address).to.be.equal(collectionAddress);645      expect(event.returnValues.from).to.be.equal(receiverEth);646      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));647      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);648649      // owner has balance = 0:650      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();651      expect(+ownerBalance).to.equal(0);652      // receiver owns token:653      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});654      expect(receiverBalance).to.contain(tokenId);655    }656  });657658  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {659    const sender = await helper.eth.createAccountWithBalance(donor);660    const tokenOwner = await helper.eth.createAccountWithBalance(donor);661    const receiverSub = minter;662    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);663664    const collection = await helper.nft.mintCollection(minter, {});665    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);666    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);667668    await collection.mintToken(minter, {Ethereum: sender});669    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});670671    // Cannot transferCross someone else's token:672    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;673    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;674    // Cannot transfer token if it does not exist:675    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;676  }));677});678679describe('NFT: Fees', () => {680  let donor: IKeyringPair;681  let alice: IKeyringPair;682  let bob: IKeyringPair;683  let charlie: IKeyringPair;684685  before(async function() {686    await usingEthPlaygrounds(async (helper, privateKey) => {687      donor = await privateKey({filename: __filename});688      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);689    });690  });691692  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {693    const owner = await helper.eth.createAccountWithBalance(donor);694    const spender = helper.eth.createAccount();695696    const collection = await helper.nft.mintCollection(alice, {});697    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});698699    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);700701    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));702    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));703  });704705  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {706    const owner = await helper.eth.createAccountWithBalance(donor);707    const spender = await helper.eth.createAccountWithBalance(donor);708709    const collection = await helper.nft.mintCollection(alice, {});710    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});711712    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);713714    await contract.methods.approve(spender, tokenId).send({from: owner});715716    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));717    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));718  });719720  itEth('Can perform transferFromCross()', async ({helper}) => {721    const collectionMinter = alice;722    const owner = bob;723    const receiver = charlie;724    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});725726    const spender = await helper.eth.createAccountWithBalance(donor, 100n);727728    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});729730    const address = helper.ethAddress.fromCollectionId(collection.collectionId);731    const contract = await helper.ethNativeContract.collection(address, 'nft');732733    await token.approve(owner, {Ethereum: spender});734735    {736      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);737      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);738      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});739      const event = result.events.Transfer;740      expect(event).to.be.like({741        address: helper.ethAddress.fromCollectionId(collection.collectionId),742        event: 'Transfer',743        returnValues: {744          from: helper.address.substrateToEth(owner.address),745          to: helper.address.substrateToEth(receiver.address),746          tokenId: token.tokenId.toString(),747        },748      });749    }750751    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});752  });753754  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {755    const owner = await helper.eth.createAccountWithBalance(donor);756    const receiver = helper.eth.createAccount();757758    const collection = await helper.nft.mintCollection(alice, {});759    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});760761    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);762763    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));764    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));765  });766});767768describe('NFT: Substrate calls', () => {769  let donor: IKeyringPair;770  let alice: IKeyringPair;771772  before(async function() {773    await usingEthPlaygrounds(async (helper, privateKey) => {774      donor = await privateKey({filename: __filename});775      [alice] = await helper.arrange.createAccounts([20n], donor);776    });777  });778779  itEth('Events emitted for mint()', async ({helper}) => {780    const collection = await helper.nft.mintCollection(alice, {});781    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);782    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');783784    const events: any = [];785    contract.events.allEvents((_: any, event: any) => {786      events.push(event);787    });788789    const {tokenId} = await collection.mintToken(alice);790    if (events.length == 0) await helper.wait.newBlocks(1);791    const event = events[0];792793    expect(event.event).to.be.equal('Transfer');794    expect(event.address).to.be.equal(collectionAddress);795    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');796    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));797    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());798  });799800  itEth('Events emitted for burn()', async ({helper}) => {801    const collection = await helper.nft.mintCollection(alice, {});802    const token = await collection.mintToken(alice);803804    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);805    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');806807    const events: any = [];808    contract.events.allEvents((_: any, event: any) => {809      events.push(event);810    });811812    await token.burn(alice);813    if (events.length == 0) await helper.wait.newBlocks(1);814    const event = events[0];815816    expect(event.event).to.be.equal('Transfer');817    expect(event.address).to.be.equal(collectionAddress);818    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));819    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');820    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());821  });822823  itEth('Events emitted for approve()', async ({helper}) => {824    const receiver = helper.eth.createAccount();825826    const collection = await helper.nft.mintCollection(alice, {});827    const token = await collection.mintToken(alice);828829    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);830    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');831832    const events: any = [];833    contract.events.allEvents((_: any, event: any) => {834      events.push(event);835    });836837    await token.approve(alice, {Ethereum: receiver});838    if (events.length == 0) await helper.wait.newBlocks(1);839    const event = events[0];840841    expect(event.event).to.be.equal('Approval');842    expect(event.address).to.be.equal(collectionAddress);843    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));844    expect(event.returnValues.approved).to.be.equal(receiver);845    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());846  });847848  itEth('Events emitted for transferFrom()', async ({helper}) => {849    const [bob] = await helper.arrange.createAccounts([10n], donor);850    const receiver = helper.eth.createAccount();851852    const collection = await helper.nft.mintCollection(alice, {});853    const token = await collection.mintToken(alice);854    await token.approve(alice, {Substrate: bob.address});855856    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);857    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');858859    const events: any = [];860    contract.events.allEvents((_: any, event: any) => {861      events.push(event);862    });863864    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});865866    if (events.length == 0) await helper.wait.newBlocks(1);867    const event = events[0];868869    expect(event.address).to.be.equal(collectionAddress);870    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));871    expect(event.returnValues.to).to.be.equal(receiver);872    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);873  });874875  itEth('Events emitted for transfer()', async ({helper}) => {876    const receiver = helper.eth.createAccount();877878    const collection = await helper.nft.mintCollection(alice, {});879    const token = await collection.mintToken(alice);880881    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);882    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');883884    const events: any = [];885    contract.events.allEvents((_: any, event: any) => {886      events.push(event);887    });888889    await token.transfer(alice, {Ethereum: receiver});890891    if (events.length == 0) await helper.wait.newBlocks(1);892    const event = events[0];893894    expect(event.address).to.be.equal(collectionAddress);895    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));896    expect(event.returnValues.to).to.be.equal(receiver);897    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);898  });899});900901describe('Common metadata', () => {902  let donor: IKeyringPair;903  let alice: IKeyringPair;904905  before(async function() {906    await usingEthPlaygrounds(async (helper, privateKey) => {907      donor = await privateKey({filename: __filename});908      [alice] = await helper.arrange.createAccounts([20n], donor);909    });910  });911912  itEth('Returns collection name', async ({helper}) => {913    const caller = await helper.eth.createAccountWithBalance(donor);914    const tokenPropertyPermissions = [{915      key: 'URI',916      permission: {917        mutable: true,918        collectionAdmin: true,919        tokenOwner: false,920      },921    }];922    const collection = await helper.nft.mintCollection(923      alice,924      {925        name: 'oh River',926        tokenPrefix: 'CHANGE',927        properties: [{key: 'ERC721Metadata', value: '1'}],928        tokenPropertyPermissions,929      },930    );931932    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);933    const name = await contract.methods.name().call();934    expect(name).to.equal('oh River');935  });936937  itEth('Returns symbol name', async ({helper}) => {938    const caller = await helper.eth.createAccountWithBalance(donor);939    const tokenPropertyPermissions = [{940      key: 'URI',941      permission: {942        mutable: true,943        collectionAdmin: true,944        tokenOwner: false,945      },946    }];947    const collection = await helper.nft.mintCollection(948      alice,949      {950        name: 'oh River',951        tokenPrefix: 'CHANGE',952        properties: [{key: 'ERC721Metadata', value: '1'}],953        tokenPropertyPermissions,954      },955    );956957    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);958    const symbol = await contract.methods.symbol().call();959    expect(symbol).to.equal('CHANGE');960  });961});962963describe('Negative tests', () => {964  let donor: IKeyringPair;965  let minter: IKeyringPair;966  let alice: IKeyringPair;967968  before(async function() {969    await usingEthPlaygrounds(async (helper, privateKey) => {970      donor = await privateKey({filename: __filename});971      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);972    });973  });974975  itEth('[negative] Cant perform burn without approval', async ({helper}) => {976    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});977978    const owner = await helper.eth.createAccountWithBalance(donor, 100n);979    const spender = await helper.eth.createAccountWithBalance(donor, 100n);980981    const token = await collection.mintToken(minter, {Ethereum: owner});982983    const address = helper.ethAddress.fromCollectionId(collection.collectionId);984    const contract = await helper.ethNativeContract.collection(address, 'nft');985986    const ownerCross = helper.ethCrossAccount.fromAddress(owner);987    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;988989    await contract.methods.setApprovalForAll(spender, true).send({from: owner});990    await contract.methods.setApprovalForAll(spender, false).send({from: owner});991992    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;993  });994995  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {996    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});997    const receiver = alice;998999    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1000    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10011002    const token = await collection.mintToken(minter, {Ethereum: owner});10031004    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1005    const contract = await helper.ethNativeContract.collection(address, 'nft');10061007    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1008    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);10091010    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;10111012    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1013    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10141015    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1016  });1017});
after · tests/src/eth/nonFungible.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// 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 {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';20import {ITokenPropertyPermission} from '../util/playgrounds/types';2122describe('Check ERC721 token URI for NFT', () => {23  let donor: IKeyringPair;2425  before(async function() {26    await usingEthPlaygrounds(async (_helper, privateKey) => {27      donor = await privateKey({filename: __filename});28    });29  });3031  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {32    const owner = await helper.eth.createAccountWithBalance(donor);33    const receiver = helper.eth.createAccount();3435    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);36    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);3738    const result = await contract.methods.mint(receiver).send();39    const tokenId = result.events.Transfer.returnValues.tokenId;40    expect(tokenId).to.be.equal('1');4142    if (propertyKey && propertyValue) {43      // Set URL or suffix44      await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();45    }4647    const event = result.events.Transfer;48    expect(event.address).to.be.equal(collectionAddress);49    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');50    expect(event.returnValues.to).to.be.equal(receiver);51    expect(event.returnValues.tokenId).to.be.equal(tokenId);5253    return {contract, nextTokenId: tokenId};54  }5556  itEth('Empty tokenURI', async ({helper}) => {57    const {contract, nextTokenId} = await setup(helper, '');58    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');59  });6061  itEth('TokenURI from url', async ({helper}) => {62    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');63    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');64  });6566  itEth('TokenURI from baseURI', async ({helper}) => {67    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');68    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');69  });7071  itEth('TokenURI from baseURI + suffix', async ({helper}) => {72    const suffix = '/some/suffix';73    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);74    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);75  });76});7778describe('NFT: Plain calls', () => {79  let donor: IKeyringPair;80  let minter: IKeyringPair;81  let bob: IKeyringPair;82  let charlie: IKeyringPair;8384  before(async function() {85    await usingEthPlaygrounds(async (helper, privateKey) => {86      donor = await privateKey({filename: __filename});87      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);88    });89  });9091  // TODO combine all minting tests in one place92  [93    'substrate' as const,94    'ethereum' as const,95  ].map(testCase => {96    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {97      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);9899      const receiverEth = helper.eth.createAccount();100      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);101      const receiverSub = bob;102      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);103104      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);105      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });106      const permissions: ITokenPropertyPermission[] = properties107        .map(p => {108          return {109            key: p.key, permission: {110              tokenOwner: false,111              collectionAdmin: true,112              mutable: false,113            },114          };115        });116117      const collection = await helper.nft.mintCollection(minter, {118        tokenPrefix: 'ethp',119        tokenPropertyPermissions: permissions,120      });121      await collection.addAdmin(minter, {Ethereum: collectionAdmin});122123      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);124      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);125      let expectedTokenId = await contract.methods.nextTokenId().call();126      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();127      let tokenId = result.events.Transfer.returnValues.tokenId;128      expect(tokenId).to.be.equal(expectedTokenId);129130      let event = result.events.Transfer;131      expect(event.address).to.be.equal(collectionAddress);132      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');133      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));134      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);135136      expectedTokenId = await contract.methods.nextTokenId().call();137      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();138      event = result.events.Transfer;139      expect(event.address).to.be.equal(collectionAddress);140      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');141      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));142      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);143144      tokenId = result.events.Transfer.returnValues.tokenId;145146      expect(tokenId).to.be.equal(expectedTokenId);147148      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties149        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));150151      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))152        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});153    });154  });155156  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {157    const nonOwner = await helper.eth.createAccountWithBalance(donor);158    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);159160    const collection = await helper.nft.mintCollection(minter);161    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);162    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');163164    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))165      .to.be.rejectedWith('PublicMintingNotAllowed');166  });167168  //TODO: CORE-302 add eth methods169  itEth.skip('Can perform mintBulk()', async ({helper}) => {170    const caller = await helper.eth.createAccountWithBalance(donor);171    const receiver = helper.eth.createAccount();172173    const collection = await helper.nft.mintCollection(minter);174    await collection.addAdmin(minter, {Ethereum: caller});175176    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);177    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);178    {179      const bulkSize = 3;180      const nextTokenId = await contract.methods.nextTokenId().call();181      expect(nextTokenId).to.be.equal('1');182      const result = await contract.methods.mintBulkWithTokenURI(183        receiver,184        Array.from({length: bulkSize}, (_, i) => (185          [+nextTokenId + i, `Test URI ${i}`]186        )),187      ).send({from: caller});188189      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);190      for (let i = 0; i < bulkSize; i++) {191        const event = events[i];192        expect(event.address).to.equal(collectionAddress);193        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');194        expect(event.returnValues.to).to.equal(receiver);195        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);196197        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);198      }199    }200  });201202  itEth('Can perform burn()', async ({helper}) => {203    const caller = await helper.eth.createAccountWithBalance(donor);204205    const collection = await helper.nft.mintCollection(minter, {});206    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});207208    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);209    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);210211    {212      const result = await contract.methods.burn(tokenId).send({from: caller});213214      const event = result.events.Transfer;215      expect(event.address).to.be.equal(collectionAddress);216      expect(event.returnValues.from).to.be.equal(caller);217      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');218      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);219    }220  });221222  itEth('Can perform approve()', async ({helper}) => {223    const owner = await helper.eth.createAccountWithBalance(donor);224    const spender = helper.eth.createAccount();225226    const collection = await helper.nft.mintCollection(minter, {});227    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});228229    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);230    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);231232    {233      const result = await contract.methods.approve(spender, tokenId).send({from: owner});234235      const event = result.events.Approval;236      expect(event.address).to.be.equal(collectionAddress);237      expect(event.returnValues.owner).to.be.equal(owner);238      expect(event.returnValues.approved).to.be.equal(spender);239      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);240    }241  });242243  itEth('Can perform setApprovalForAll()', async ({helper}) => {244    const owner = await helper.eth.createAccountWithBalance(donor);245    const operator = helper.eth.createAccount();246247    const collection = await helper.nft.mintCollection(minter, {});248249    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);250    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);251252    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();253    expect(approvedBefore).to.be.equal(false);254255    {256      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});257258      expect(result.events.ApprovalForAll).to.be.like({259        address: collectionAddress,260        event: 'ApprovalForAll',261        returnValues: {262          owner,263          operator,264          approved: true,265        },266      });267268      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();269      expect(approvedAfter).to.be.equal(true);270    }271272    {273      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});274275      expect(result.events.ApprovalForAll).to.be.like({276        address: collectionAddress,277        event: 'ApprovalForAll',278        returnValues: {279          owner,280          operator,281          approved: false,282        },283      });284285      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();286      expect(approvedAfter).to.be.equal(false);287    }288  });289290  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {291    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});292293    const owner = await helper.eth.createAccountWithBalance(donor);294    const operator = await helper.eth.createAccountWithBalance(donor, 100n);295296    const token = await collection.mintToken(minter, {Ethereum: owner});297298    const address = helper.ethAddress.fromCollectionId(collection.collectionId);299    const contract = await helper.ethNativeContract.collection(address, 'nft');300301    {302      await contract.methods.setApprovalForAll(operator, true).send({from: owner});303      const ownerCross = helper.ethCrossAccount.fromAddress(owner);304      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});305      const events = result.events.Transfer;306307      expect(events).to.be.like({308        address,309        event: 'Transfer',310        returnValues: {311          from: owner,312          to: '0x0000000000000000000000000000000000000000',313          tokenId: token.tokenId.toString(),314        },315      });316    }317318    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;319  });320321  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {322    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});323324    const owner = await helper.eth.createAccountWithBalance(donor);325    const operator = await helper.eth.createAccountWithBalance(donor);326    const receiver = charlie;327328    const token = await collection.mintToken(minter, {Ethereum: owner});329330    const address = helper.ethAddress.fromCollectionId(collection.collectionId);331    const contract = await helper.ethNativeContract.collection(address, 'nft');332333    {334      await contract.methods.setApprovalForAll(operator, true).send({from: owner});335      const ownerCross = helper.ethCrossAccount.fromAddress(owner);336      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);337      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});338      const event = result.events.Transfer;339      expect(event).to.be.like({340        address: helper.ethAddress.fromCollectionId(collection.collectionId),341        event: 'Transfer',342        returnValues: {343          from: owner,344          to: helper.address.substrateToEth(receiver.address),345          tokenId: token.tokenId.toString(),346        },347      });348    }349350    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});351  });352353  itEth('Can perform burnFromCross()', async ({helper}) => {354    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});355    const ownerSub = bob;356    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);357    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);358    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);359360    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);361    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);362363    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});364    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});365366    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);367    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');368369    // Approve tokens from substrate and ethereum:370    await token1.approve(ownerSub, {Ethereum: burnerEth});371    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});372373    // can burnFromCross:374    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});375    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});376    const events1 = result1.events.Transfer;377    const events2 = result2.events.Transfer;378379    // Check events for burnFromCross (substrate and ethereum):380    [381      [events1, token1, helper.address.substrateToEth(ownerSub.address)],382      [events2, token2, ownerEth],383    ].map(burnData => {384      expect(burnData[0]).to.be.like({385        address: collectionAddress,386        event: 'Transfer',387        returnValues: {388          from: burnData[2],389          to: '0x0000000000000000000000000000000000000000',390          tokenId: burnData[1].tokenId.toString(),391        },392      });393    });394395    expect(await token1.doesExist()).to.be.false;396    expect(await token2.doesExist()).to.be.false;397  });398399  // TODO combine all approve tests in one place400  itEth('Can perform approveCross()', async ({helper}) => {401    // arrange: create accounts402    const owner = await helper.eth.createAccountWithBalance(donor, 100n);403    const ownerCross = helper.ethCrossAccount.fromAddress(owner);404    const receiverSub = charlie;405    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);406    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);407    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);408409    // arrange: create collection and tokens:410    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});411    const token1 = await collection.mintToken(minter, {Ethereum: owner});412    const token2 = await collection.mintToken(minter, {Ethereum: owner});413414    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');415416    // Can approveCross substrate and ethereum address:417    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});418    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});419    const eventSub = resultSub.events.Approval;420    const eventEth = resultEth.events.Approval;421    expect(eventSub).to.be.like({422      address: helper.ethAddress.fromCollectionId(collection.collectionId),423      event: 'Approval',424      returnValues: {425        owner,426        approved: helper.address.substrateToEth(receiverSub.address),427        tokenId: token1.tokenId.toString(),428      },429    });430    expect(eventEth).to.be.like({431      address: helper.ethAddress.fromCollectionId(collection.collectionId),432      event: 'Approval',433      returnValues: {434        owner,435        approved: receiverEth,436        tokenId: token2.tokenId.toString(),437      },438    });439440    // Substrate address can transferFrom approved tokens:441    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});442    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});443    // Ethereum address can transferFromCross approved tokens:444    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});445    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});446  });447448  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {449    const nonOwner = await helper.eth.createAccountWithBalance(donor);450    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);451    const owner = await helper.eth.createAccountWithBalance(donor);452    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});453    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');454    const token = await collection.mintToken(minter, {Ethereum: owner});455456    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');457  });458459  itEth('Can reaffirm approved address', async ({helper}) => {460    const owner = await helper.eth.createAccountWithBalance(donor, 100n);461    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);462    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);463    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);464    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);465    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});466    const token1 = await collection.mintToken(minter, {Ethereum: owner});467    const token2 = await collection.mintToken(minter, {Ethereum: owner});468    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');469470    // Can approve and reaffirm approved address:471    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});472    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});473474    // receiver1 cannot transferFrom:475    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;476    // receiver2 can transferFrom:477    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});478479    // can set approved address to self address to remove approval:480    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});481    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});482483    // receiver1 cannot transfer token anymore:484    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;485  });486487  itEth('Can perform transferFrom()', async ({helper}) => {488    const owner = await helper.eth.createAccountWithBalance(donor);489    const spender = await helper.eth.createAccountWithBalance(donor);490    const receiver = helper.eth.createAccount();491492    const collection = await helper.nft.mintCollection(minter, {});493    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});494495    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);496    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);497498    await contract.methods.approve(spender, tokenId).send({from: owner});499500    {501      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});502503      const event = result.events.Transfer;504      expect(event.address).to.be.equal(collectionAddress);505      expect(event.returnValues.from).to.be.equal(owner);506      expect(event.returnValues.to).to.be.equal(receiver);507      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);508    }509510    {511      const balance = await contract.methods.balanceOf(receiver).call();512      expect(+balance).to.equal(1);513    }514515    {516      const balance = await contract.methods.balanceOf(owner).call();517      expect(+balance).to.equal(0);518    }519  });520521  itEth('Can perform transferFromCross()', async ({helper}) => {522    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});523524    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);525    const spender = await helper.eth.createAccountWithBalance(donor);526527    const token = await collection.mintToken(minter, {Substrate: owner.address});528529    const address = helper.ethAddress.fromCollectionId(collection.collectionId);530    const contract = await helper.ethNativeContract.collection(address, 'nft');531532    await token.approve(owner, {Ethereum: spender});533534    {535      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);536      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);537      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});538      const event = result.events.Transfer;539      expect(event).to.be.like({540        address: helper.ethAddress.fromCollectionId(collection.collectionId),541        event: 'Transfer',542        returnValues: {543          from: helper.address.substrateToEth(owner.address),544          to: helper.address.substrateToEth(receiver.address),545          tokenId: token.tokenId.toString(),546        },547      });548    }549550    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});551  });552553  itEth('Can perform transfer()', async ({helper}) => {554    const collection = await helper.nft.mintCollection(minter, {});555    const owner = await helper.eth.createAccountWithBalance(donor);556    const receiver = helper.eth.createAccount();557558    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});559560    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);561    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);562563    {564      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});565566      const event = result.events.Transfer;567      expect(event.address).to.be.equal(collectionAddress);568      expect(event.returnValues.from).to.be.equal(owner);569      expect(event.returnValues.to).to.be.equal(receiver);570      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);571    }572573    {574      const balance = await contract.methods.balanceOf(owner).call();575      expect(+balance).to.equal(0);576    }577578    {579      const balance = await contract.methods.balanceOf(receiver).call();580      expect(+balance).to.equal(1);581    }582  });583584  itEth('Can perform transferCross()', async ({helper}) => {585    const collection = await helper.nft.mintCollection(minter, {});586    const owner = await helper.eth.createAccountWithBalance(donor);587    const receiverEth = await helper.eth.createAccountWithBalance(donor);588    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);589    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);590591    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});592593    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);594    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);595596    {597      // Can transferCross to ethereum address:598      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});599      // Check events:600      const event = result.events.Transfer;601      expect(event.address).to.be.equal(collectionAddress);602      expect(event.returnValues.from).to.be.equal(owner);603      expect(event.returnValues.to).to.be.equal(receiverEth);604      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);605606      // owner has balance = 0:607      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();608      expect(+ownerBalance).to.equal(0);609      // receiver owns token:610      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();611      expect(+receiverBalance).to.equal(1);612      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});613    }614615    {616      // Can transferCross to substrate address:617      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});618      // Check events:619      const event = substrateResult.events.Transfer;620      expect(event.address).to.be.equal(collectionAddress);621      expect(event.returnValues.from).to.be.equal(receiverEth);622      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));623      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);624625      // owner has balance = 0:626      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();627      expect(+ownerBalance).to.equal(0);628      // receiver owns token:629      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});630      expect(receiverBalance).to.contain(tokenId);631    }632  });633634  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {635    const sender = await helper.eth.createAccountWithBalance(donor);636    const tokenOwner = await helper.eth.createAccountWithBalance(donor);637    const receiverSub = minter;638    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);639640    const collection = await helper.nft.mintCollection(minter, {});641    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);642    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);643644    await collection.mintToken(minter, {Ethereum: sender});645    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});646647    // Cannot transferCross someone else's token:648    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;649    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;650    // Cannot transfer token if it does not exist:651    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;652  }));653});654655describe('NFT: Fees', () => {656  let donor: IKeyringPair;657  let alice: IKeyringPair;658  let bob: IKeyringPair;659  let charlie: IKeyringPair;660661  before(async function() {662    await usingEthPlaygrounds(async (helper, privateKey) => {663      donor = await privateKey({filename: __filename});664      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);665    });666  });667668  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {669    const owner = await helper.eth.createAccountWithBalance(donor);670    const spender = helper.eth.createAccount();671672    const collection = await helper.nft.mintCollection(alice, {});673    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});674675    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);676677    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));678    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));679  });680681  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {682    const owner = await helper.eth.createAccountWithBalance(donor);683    const spender = await helper.eth.createAccountWithBalance(donor);684685    const collection = await helper.nft.mintCollection(alice, {});686    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});687688    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);689690    await contract.methods.approve(spender, tokenId).send({from: owner});691692    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));693    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));694  });695696  itEth('Can perform transferFromCross()', async ({helper}) => {697    const collectionMinter = alice;698    const owner = bob;699    const receiver = charlie;700    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});701702    const spender = await helper.eth.createAccountWithBalance(donor, 100n);703704    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});705706    const address = helper.ethAddress.fromCollectionId(collection.collectionId);707    const contract = await helper.ethNativeContract.collection(address, 'nft');708709    await token.approve(owner, {Ethereum: spender});710711    {712      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);713      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);714      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});715      const event = result.events.Transfer;716      expect(event).to.be.like({717        address: helper.ethAddress.fromCollectionId(collection.collectionId),718        event: 'Transfer',719        returnValues: {720          from: helper.address.substrateToEth(owner.address),721          to: helper.address.substrateToEth(receiver.address),722          tokenId: token.tokenId.toString(),723        },724      });725    }726727    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});728  });729730  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {731    const owner = await helper.eth.createAccountWithBalance(donor);732    const receiver = helper.eth.createAccount();733734    const collection = await helper.nft.mintCollection(alice, {});735    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});736737    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);738739    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));740    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));741  });742});743744describe('NFT: Substrate calls', () => {745  let donor: IKeyringPair;746  let alice: IKeyringPair;747748  before(async function() {749    await usingEthPlaygrounds(async (helper, privateKey) => {750      donor = await privateKey({filename: __filename});751      [alice] = await helper.arrange.createAccounts([20n], donor);752    });753  });754755  itEth('Events emitted for mint()', async ({helper}) => {756    const collection = await helper.nft.mintCollection(alice, {});757    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);758    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');759760    const events: any = [];761    contract.events.allEvents((_: any, event: any) => {762      events.push(event);763    });764765    const {tokenId} = await collection.mintToken(alice);766    if (events.length == 0) await helper.wait.newBlocks(1);767    const event = events[0];768769    expect(event.event).to.be.equal('Transfer');770    expect(event.address).to.be.equal(collectionAddress);771    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');772    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));773    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());774  });775776  itEth('Events emitted for burn()', async ({helper}) => {777    const collection = await helper.nft.mintCollection(alice, {});778    const token = await collection.mintToken(alice);779780    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);781    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');782783    const events: any = [];784    contract.events.allEvents((_: any, event: any) => {785      events.push(event);786    });787788    await token.burn(alice);789    if (events.length == 0) await helper.wait.newBlocks(1);790    const event = events[0];791792    expect(event.event).to.be.equal('Transfer');793    expect(event.address).to.be.equal(collectionAddress);794    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));795    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');796    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());797  });798799  itEth('Events emitted for approve()', async ({helper}) => {800    const receiver = helper.eth.createAccount();801802    const collection = await helper.nft.mintCollection(alice, {});803    const token = await collection.mintToken(alice);804805    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);806    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');807808    const events: any = [];809    contract.events.allEvents((_: any, event: any) => {810      events.push(event);811    });812813    await token.approve(alice, {Ethereum: receiver});814    if (events.length == 0) await helper.wait.newBlocks(1);815    const event = events[0];816817    expect(event.event).to.be.equal('Approval');818    expect(event.address).to.be.equal(collectionAddress);819    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));820    expect(event.returnValues.approved).to.be.equal(receiver);821    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());822  });823824  itEth('Events emitted for transferFrom()', async ({helper}) => {825    const [bob] = await helper.arrange.createAccounts([10n], donor);826    const receiver = helper.eth.createAccount();827828    const collection = await helper.nft.mintCollection(alice, {});829    const token = await collection.mintToken(alice);830    await token.approve(alice, {Substrate: bob.address});831832    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);833    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');834835    const events: any = [];836    contract.events.allEvents((_: any, event: any) => {837      events.push(event);838    });839840    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});841842    if (events.length == 0) await helper.wait.newBlocks(1);843    const event = events[0];844845    expect(event.address).to.be.equal(collectionAddress);846    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));847    expect(event.returnValues.to).to.be.equal(receiver);848    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);849  });850851  itEth('Events emitted for transfer()', async ({helper}) => {852    const receiver = helper.eth.createAccount();853854    const collection = await helper.nft.mintCollection(alice, {});855    const token = await collection.mintToken(alice);856857    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);858    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');859860    const events: any = [];861    contract.events.allEvents((_: any, event: any) => {862      events.push(event);863    });864865    await token.transfer(alice, {Ethereum: receiver});866867    if (events.length == 0) await helper.wait.newBlocks(1);868    const event = events[0];869870    expect(event.address).to.be.equal(collectionAddress);871    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));872    expect(event.returnValues.to).to.be.equal(receiver);873    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);874  });875});876877describe('Common metadata', () => {878  let donor: IKeyringPair;879  let alice: IKeyringPair;880881  before(async function() {882    await usingEthPlaygrounds(async (helper, privateKey) => {883      donor = await privateKey({filename: __filename});884      [alice] = await helper.arrange.createAccounts([20n], donor);885    });886  });887888  itEth('Returns collection name', async ({helper}) => {889    const caller = await helper.eth.createAccountWithBalance(donor);890    const tokenPropertyPermissions = [{891      key: 'URI',892      permission: {893        mutable: true,894        collectionAdmin: true,895        tokenOwner: false,896      },897    }];898    const collection = await helper.nft.mintCollection(899      alice,900      {901        name: 'oh River',902        tokenPrefix: 'CHANGE',903        properties: [{key: 'ERC721Metadata', value: '1'}],904        tokenPropertyPermissions,905      },906    );907908    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);909    const name = await contract.methods.name().call();910    expect(name).to.equal('oh River');911  });912913  itEth('Returns symbol name', async ({helper}) => {914    const caller = await helper.eth.createAccountWithBalance(donor);915    const tokenPropertyPermissions = [{916      key: 'URI',917      permission: {918        mutable: true,919        collectionAdmin: true,920        tokenOwner: false,921      },922    }];923    const collection = await helper.nft.mintCollection(924      alice,925      {926        name: 'oh River',927        tokenPrefix: 'CHANGE',928        properties: [{key: 'ERC721Metadata', value: '1'}],929        tokenPropertyPermissions,930      },931    );932933    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);934    const symbol = await contract.methods.symbol().call();935    expect(symbol).to.equal('CHANGE');936  });937});938939describe('Negative tests', () => {940  let donor: IKeyringPair;941  let minter: IKeyringPair;942  let alice: IKeyringPair;943944  before(async function() {945    await usingEthPlaygrounds(async (helper, privateKey) => {946      donor = await privateKey({filename: __filename});947      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);948    });949  });950951  itEth('[negative] Cant perform burn without approval', async ({helper}) => {952    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});953954    const owner = await helper.eth.createAccountWithBalance(donor, 100n);955    const spender = await helper.eth.createAccountWithBalance(donor, 100n);956957    const token = await collection.mintToken(minter, {Ethereum: owner});958959    const address = helper.ethAddress.fromCollectionId(collection.collectionId);960    const contract = await helper.ethNativeContract.collection(address, 'nft');961962    const ownerCross = helper.ethCrossAccount.fromAddress(owner);963    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;964965    await contract.methods.setApprovalForAll(spender, true).send({from: owner});966    await contract.methods.setApprovalForAll(spender, false).send({from: owner});967968    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;969  });970971  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {972    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});973    const receiver = alice;974975    const owner = await helper.eth.createAccountWithBalance(donor, 100n);976    const spender = await helper.eth.createAccountWithBalance(donor, 100n);977978    const token = await collection.mintToken(minter, {Ethereum: owner});979980    const address = helper.ethAddress.fromCollectionId(collection.collectionId);981    const contract = await helper.ethNativeContract.collection(address, 'nft');982983    const ownerCross = helper.ethCrossAccount.fromAddress(owner);984    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);985986    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;987988    await contract.methods.setApprovalForAll(spender, true).send({from: owner});989    await contract.methods.setApprovalForAll(spender, false).send({from: owner});990991    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;992  });993});
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -34,25 +34,6 @@
     });
   });
 
-  itEth('Can perform mint() & crossOwnerOf()', async ({helper}) => {
-    const owner = await helper.eth.createAccountWithBalance(donor);
-    const receiver = helper.eth.createAccount();
-    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', '');
-    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
-
-    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();
-
-    const event = result.events.Transfer;
-    expect(event.address).to.equal(collectionAddress);
-    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
-    expect(event.returnValues.to).to.equal(receiver);
-    const tokenId = event.returnValues.tokenId;
-    expect(tokenId).to.be.equal('1');
-
-    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
-    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
-  });
-
   [
     'substrate' as const,
     'ethereum' as const,
addedtests/src/eth/tokens/callMethodsERC20.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/tokens/callMethodsERC20.test.ts
@@ -0,0 +1,91 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {Pallets, requirePalletsOrSkip} from '../../util';
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+[
+  {mode: 'ft' as const, requiredPallets: []},
+  {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+].map(testCase => {
+  describe(`${testCase.mode.toUpperCase()}: ERC-20 call methods`, () => {
+    let donor: IKeyringPair;
+
+    before(async function() {
+      await usingEthPlaygrounds(async (helper, privateKey) => {
+        requirePalletsOrSkip(this, helper, testCase.requiredPallets);
+        donor = await privateKey({filename: __filename});
+      });
+    });
+
+    itEth('totalSupply', async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+      const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller];
+
+      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6');
+      if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
+
+      // Use collection contract for FT or token contract for RFT:
+      const contract = testCase.mode === 'ft'
+        ? collection
+        : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
+
+      // Mint tokens:
+      testCase.mode === 'ft'
+        ? await contract.methods.mint(...mintingParams).send({from: caller})
+        : await contract.methods.repartition(200).send({from: caller});
+
+      const totalSupply = await contract.methods.totalSupply().call();
+      expect(totalSupply).to.equal('200');
+    });
+
+    itEth('balanceOf', async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+      const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller];
+
+      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
+      if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
+
+      // Use collection contract for FT or token contract for RFT:
+      const contract = testCase.mode === 'ft'
+        ? collection
+        : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
+
+      // Mint tokens:
+      testCase.mode === 'ft'
+        ? await contract.methods.mint(...mintingParams).send({from: caller})
+        : await contract.methods.repartition(200).send({from: caller});
+
+      const balance = await contract.methods.balanceOf(caller).call();
+      expect(balance).to.equal('200');
+    });
+
+    itEth('decimals', async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
+      if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
+
+      // Use collection contract for FT or token contract for RFT:
+      const contract = testCase.mode === 'ft'
+        ? collection
+        : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
+
+      const decimals = await contract.methods.decimals().call();
+      expect(decimals).to.equal(testCase.mode === 'rft' ? '0' : '18');
+    });
+  });
+});
\ No newline at end of file
addedtests/src/eth/tokens/callMethodsERC721.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/tokens/callMethodsERC721.test.ts
@@ -0,0 +1,143 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {Pallets} from '../../util';
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+
+describe('ERC-721 call methods', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+    });
+  });
+
+  [
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'nft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: name/symbol/description`, testCase.requiredPallets, async ({helper}) => {
+      const callerEth = await helper.eth.createAccountWithBalance(donor);
+      const [callerSub] = await helper.arrange.createAccounts([100n], donor);
+      const [name, description, tokenPrefix] = ['Name', 'Description', 'Symbol'];
+
+      const {collection: collectionEth} = await helper.eth.createCollection(testCase.mode, callerEth, name, description, tokenPrefix);
+      await collectionEth.methods.mint(callerEth).send({from: callerEth});
+      const {collectionId} = await helper[testCase.mode].mintCollection(callerSub, {name, description, tokenPrefix});
+      const collectionSub = await helper.ethNativeContract.collectionById(collectionId, testCase.mode, callerEth);
+
+      // Can get name/symbol/description for Eth collection
+      expect(await collectionEth.methods.name().call()).to.eq(name);
+      expect(await collectionEth.methods.symbol().call()).to.eq(tokenPrefix);
+      expect(await collectionEth.methods.description().call()).to.eq(description);
+      // Can get name/symbol/description for Sub collection
+      expect(await collectionSub.methods.name().call()).to.eq(name);
+      expect(await collectionSub.methods.symbol().call()).to.eq(tokenPrefix);
+      expect(await collectionSub.methods.description().call()).to.eq(description);
+    });
+  });
+
+  [
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'nft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: totalSupply`, testCase.requiredPallets, async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+
+      const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6');
+      await collection.methods.mint(caller).send({from: caller});
+
+      const totalSupply = await collection.methods.totalSupply().call();
+      expect(totalSupply).to.equal('1');
+    });
+  });
+
+  [
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'nft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: balanceOf`, testCase.requiredPallets, async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+
+      const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
+      await collection.methods.mint(caller).send({from: caller});
+      await collection.methods.mint(caller).send({from: caller});
+      await collection.methods.mint(caller).send({from: caller});
+
+      const balance = await collection.methods.balanceOf(caller).call();
+      expect(balance).to.equal('3');
+    });
+  });
+
+  [
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'nft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: ownerOf`, testCase.requiredPallets, async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+      const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf', '6', '6');
+
+      const result = await collection.methods.mint(caller).send();
+      const tokenId = result.events.Transfer.returnValues.tokenId;
+
+      const owner = await collection.methods.ownerOf(tokenId).call();
+      expect(owner).to.equal(caller);
+    });
+  });
+
+  [
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    // TODO {mode: 'nft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth(`${testCase.mode.toUpperCase()}: ownerOf after burn`, async ({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+      const receiver = helper.eth.createAccount();
+      const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf-AfterBurn', '6', '6');
+
+      const result = await collection.methods.mint(caller).send();
+      const tokenId = result.events.Transfer.returnValues.tokenId;
+      const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);
+
+      await tokenContract.methods.repartition(2).send();
+      await tokenContract.methods.transfer(receiver, 1).send();
+
+      await tokenContract.methods.burnFrom(caller, 1).send();
+
+      const owner = await collection.methods.ownerOf(tokenId).call();
+      expect(owner).to.equal(receiver);
+    });
+  });
+
+  itEth('RFT: ownerOf for partial ownership', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiver = helper.eth.createAccount();
+    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');
+    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+
+    const result = await contract.methods.mint(caller).send();
+    const tokenId = result.events.Transfer.returnValues.tokenId;
+    const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);
+
+    await tokenContract.methods.repartition(2).send();
+    await tokenContract.methods.transfer(receiver, 1).send();
+
+    const owner = await contract.methods.ownerOf(tokenId).call();
+    expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');
+  });
+});
addedtests/src/eth/tokens/minting.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/tokens/minting.test.ts
@@ -0,0 +1,167 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets} from '../../util';
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+
+
+describe('Minting tokens', () => {
+  let donor: IKeyringPair;
+  let alice: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+      [alice] = await helper.arrange.createAccounts([30n, 20n], donor);
+    });
+  });
+
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'ft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth(`${testCase.mode.toUpperCase()}: Can mint() for Substrate collection`, async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const receiver = helper.eth.createAccount();
+      const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver];
+
+      const collection = await helper[testCase.mode].mintCollection(alice);
+      await collection.addAdmin(alice, {Ethereum: owner});
+
+      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+      const contract = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);
+
+      const result = await contract.methods.mint(...mintingParams).send({from: owner});
+
+      // Check events:
+      const event = result.events.Transfer;
+      expect(event.address).to.equal(collectionAddress);
+      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+      expect(event.returnValues.to).to.equal(receiver);
+      if (testCase.mode === 'ft')
+        expect(event.returnValues.value).to.equal('100');
+
+      // Check token exist:
+      if(testCase.mode === 'ft') {
+        expect(await helper.ft.getBalance(collection.collectionId, {Ethereum: receiver})).to.eq(100n);
+      } else {
+        const tokenId = event.returnValues.tokenId;
+        expect(tokenId).to.be.equal('1');
+        expect(await helper.collection.getLastTokenId(collection.collectionId)).to.eq(1);
+        expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+      }
+    });
+  });
+
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'ft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth(`${testCase.mode.toUpperCase()}: Can mint() for Ethereum collection`, async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const receiver = helper.eth.createAccount();
+      const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver];
+
+      const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'Name', 'Desc', 'Prefix');
+
+      const result = await collection.methods.mint(...mintingParams).send({from: owner});
+
+      // Check events:
+      const event = result.events.Transfer;
+      expect(event.address).to.equal(collectionAddress);
+      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+      expect(event.returnValues.to).to.equal(receiver);
+      if (testCase.mode === 'ft')
+        expect(event.returnValues.value).to.equal('100');
+
+      // Check token exist:
+      if(testCase.mode === 'ft') {
+        expect(await helper.ft.getBalance(collectionId, {Ethereum: receiver})).to.eq(100n);
+      } else {
+        const tokenId = event.returnValues.tokenId;
+        expect(tokenId).to.be.equal('1');
+        expect(await helper.collection.getLastTokenId(collectionId)).to.eq(1);
+        expect(await collection.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+      }
+    });
+  });
+
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+    {mode: 'ft' as const, requiredPallets: []},
+  ].map(testCase => {
+    itEth(`${testCase.mode.toUpperCase()}: Can mint() for Ethereum collection`, async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const receiver = helper.eth.createAccount();
+      const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver];
+
+      const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'Name', 'Desc', 'Prefix');
+
+      const result = await collection.methods.mint(...mintingParams).send({from: owner});
+
+      // Check events:
+      const event = result.events.Transfer;
+      expect(event.address).to.equal(collectionAddress);
+      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+      expect(event.returnValues.to).to.equal(receiver);
+      if (testCase.mode === 'ft')
+        expect(event.returnValues.value).to.equal('100');
+
+      // Check token exist:
+      if(testCase.mode === 'ft') {
+        expect(await helper.ft.getBalance(collectionId, {Ethereum: receiver})).to.eq(100n);
+      } else {
+        const tokenId = event.returnValues.tokenId;
+        expect(tokenId).to.be.equal('1');
+        expect(await helper.collection.getLastTokenId(collectionId)).to.eq(1);
+        expect(await collection.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+      }
+    });
+  });
+
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+  ].map(testCase => {
+    itEth(`${testCase.mode.toUpperCase()}: Can mintWithTokenURI() for Ethereum collection`, async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const receiver = helper.eth.createAccount();
+
+      const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');
+      const contract = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);
+
+      const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();
+      const tokenId = result.events.Transfer.returnValues.tokenId;
+      expect(tokenId).to.be.equal('1');
+
+      const event = result.events.Transfer;
+      expect(event.address).to.be.equal(collectionAddress);
+      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');
+      expect(event.returnValues.to).to.be.equal(receiver);
+
+      expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
+      expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+      // TODO: this wont work right now, need release 919000 first
+      // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();
+      // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();
+      // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);
+    });
+  });
+});
\ No newline at end of file