git.delta.rocks / unique-network / refs/commits / 8a76dd7aa345

difftreelog

Combine call tests

Max Andreev2023-01-12parent: #32d4fdc.patch.diff
in: master

7 files changed

addedtests/src/eth/collections/callMethodsERC20.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/collections/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/collections/callMethodsERC721.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/collections/callMethodsERC721.test.ts
@@ -0,0 +1,127 @@
+// 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
@@ -17,38 +17,6 @@
 import {expect, itEth, usingEthPlaygrounds} from './util';
 import {IKeyringPair} from '@polkadot/types/types';
 
-describe('Fungible: Information getting', () => {
-  let donor: IKeyringPair;
-  let alice: IKeyringPair;
-
-  before(async function() {
-    await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = await privateKey({filename: __filename});
-      [alice] = await helper.arrange.createAccounts([20n], donor);
-    });
-  });
-
-  itEth('totalSupply', async ({helper}) => {
-    const caller = await helper.eth.createAccountWithBalance(donor);
-    const collection = await helper.ft.mintCollection(alice);
-    await collection.mint(alice, 200n);
-
-    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'ft', 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 collection = await helper.ft.mintCollection(alice);
-    await collection.mint(alice, 200n, {Ethereum: caller});
-
-    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);
-    const balance = await contract.methods.balanceOf(caller).call();
-    expect(balance).to.equal('200');
-  });
-});
-
 describe('Fungible: Plain calls', () => {
   let donor: IKeyringPair;
   let alice: IKeyringPair;
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';212223describe('NFT: Information getting', () => {24  let donor: IKeyringPair;25  let alice: IKeyringPair;2627  before(async function() {28    await usingEthPlaygrounds(async (helper, privateKey) => {29      donor = await privateKey({filename: __filename});30      [alice] = await helper.arrange.createAccounts([10n], donor);31    });32  });3334  itEth('totalSupply', async ({helper}) => {35    const collection = await helper.nft.mintCollection(alice, {});36    await collection.mintToken(alice);3738    const caller = await helper.eth.createAccountWithBalance(donor);3940    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);41    const totalSupply = await contract.methods.totalSupply().call();4243    expect(totalSupply).to.equal('1');44  });4546  itEth('balanceOf', async ({helper}) => {47    const collection = await helper.nft.mintCollection(alice, {});48    const caller = await helper.eth.createAccountWithBalance(donor);4950    await collection.mintToken(alice, {Ethereum: caller});51    await collection.mintToken(alice, {Ethereum: caller});52    await collection.mintToken(alice, {Ethereum: caller});5354    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);55    const balance = await contract.methods.balanceOf(caller).call();5657    expect(balance).to.equal('3');58  });5960  itEth('ownerOf', async ({helper}) => {61    const collection = await helper.nft.mintCollection(alice, {});62    const caller = await helper.eth.createAccountWithBalance(donor);6364    const token = await collection.mintToken(alice, {Ethereum: caller});6566    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6768    const owner = await contract.methods.ownerOf(token.tokenId).call();6970    expect(owner).to.equal(caller);71  });7273  itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {74    const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});75    const caller = helper.eth.createAccount();7677    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7879    expect(await contract.methods.name().call()).to.equal('test');80    expect(await contract.methods.symbol().call()).to.equal('TEST');81  });82});8384describe('Check ERC721 token URI for NFT', () => {85  let donor: IKeyringPair;8687  before(async function() {88    await usingEthPlaygrounds(async (_helper, privateKey) => {89      donor = await privateKey({filename: __filename});90    });91  });9293  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {94    const owner = await helper.eth.createAccountWithBalance(donor);95    const receiver = helper.eth.createAccount();9697    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);98    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);99100    const result = await contract.methods.mint(receiver).send();101    const tokenId = result.events.Transfer.returnValues.tokenId;102    expect(tokenId).to.be.equal('1');103104    if (propertyKey && propertyValue) {105      // Set URL or suffix106      await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();107    }108109    const event = result.events.Transfer;110    expect(event.address).to.be.equal(collectionAddress);111    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');112    expect(event.returnValues.to).to.be.equal(receiver);113    expect(event.returnValues.tokenId).to.be.equal(tokenId);114115    return {contract, nextTokenId: tokenId};116  }117118  itEth('Empty tokenURI', async ({helper}) => {119    const {contract, nextTokenId} = await setup(helper, '');120    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');121  });122123  itEth('TokenURI from url', async ({helper}) => {124    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');125    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');126  });127128  itEth('TokenURI from baseURI', async ({helper}) => {129    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');130    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');131  });132133  itEth('TokenURI from baseURI + suffix', async ({helper}) => {134    const suffix = '/some/suffix';135    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);136    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);137  });138});139140describe('NFT: Plain calls', () => {141  let donor: IKeyringPair;142  let minter: IKeyringPair;143  let bob: IKeyringPair;144  let charlie: IKeyringPair;145146  before(async function() {147    await usingEthPlaygrounds(async (helper, privateKey) => {148      donor = await privateKey({filename: __filename});149      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);150    });151  });152153  itEth('Can perform mint() & get crossOwner()', async ({helper}) => {154    const owner = await helper.eth.createAccountWithBalance(donor);155    const receiver = helper.eth.createAccount();156157    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');158    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);159160    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();161    const tokenId = result.events.Transfer.returnValues.tokenId;162    expect(tokenId).to.be.equal('1');163164    const event = result.events.Transfer;165    expect(event.address).to.be.equal(collectionAddress);166    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');167    expect(event.returnValues.to).to.be.equal(receiver);168169    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');170    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);171    // TODO: this wont work right now, need release 919000 first172    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();173    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();174    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);175  });176177  // TODO combine all minting tests in one place178  [179    'substrate' as const,180    'ethereum' as const,181  ].map(testCase => {182    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {183      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);184185      const receiverEth = helper.eth.createAccount();186      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);187      const receiverSub = bob;188      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);189190      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);191      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });192      const permissions: ITokenPropertyPermission[] = properties193        .map(p => {194          return {195            key: p.key, permission: {196              tokenOwner: false,197              collectionAdmin: true,198              mutable: false,199            },200          };201        });202203      const collection = await helper.nft.mintCollection(minter, {204        tokenPrefix: 'ethp',205        tokenPropertyPermissions: permissions,206      });207      await collection.addAdmin(minter, {Ethereum: collectionAdmin});208209      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);210      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);211      let expectedTokenId = await contract.methods.nextTokenId().call();212      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();213      let tokenId = result.events.Transfer.returnValues.tokenId;214      expect(tokenId).to.be.equal(expectedTokenId);215216      let event = result.events.Transfer;217      expect(event.address).to.be.equal(collectionAddress);218      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');219      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));220      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);221222      expectedTokenId = await contract.methods.nextTokenId().call();223      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();224      event = result.events.Transfer;225      expect(event.address).to.be.equal(collectionAddress);226      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');227      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));228      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);229230      tokenId = result.events.Transfer.returnValues.tokenId;231232      expect(tokenId).to.be.equal(expectedTokenId);233234      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties235        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));236237      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))238        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});239    });240  });241242  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {243    const nonOwner = await helper.eth.createAccountWithBalance(donor);244    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);245246    const collection = await helper.nft.mintCollection(minter);247    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);248    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');249250    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))251      .to.be.rejectedWith('PublicMintingNotAllowed');252  });253254  //TODO: CORE-302 add eth methods255  itEth.skip('Can perform mintBulk()', async ({helper}) => {256    const caller = await helper.eth.createAccountWithBalance(donor);257    const receiver = helper.eth.createAccount();258259    const collection = await helper.nft.mintCollection(minter);260    await collection.addAdmin(minter, {Ethereum: caller});261262    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);263    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);264    {265      const bulkSize = 3;266      const nextTokenId = await contract.methods.nextTokenId().call();267      expect(nextTokenId).to.be.equal('1');268      const result = await contract.methods.mintBulkWithTokenURI(269        receiver,270        Array.from({length: bulkSize}, (_, i) => (271          [+nextTokenId + i, `Test URI ${i}`]272        )),273      ).send({from: caller});274275      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);276      for (let i = 0; i < bulkSize; i++) {277        const event = events[i];278        expect(event.address).to.equal(collectionAddress);279        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');280        expect(event.returnValues.to).to.equal(receiver);281        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);282283        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);284      }285    }286  });287288  itEth('Can perform burn()', async ({helper}) => {289    const caller = await helper.eth.createAccountWithBalance(donor);290291    const collection = await helper.nft.mintCollection(minter, {});292    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});293294    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);295    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);296297    {298      const result = await contract.methods.burn(tokenId).send({from: caller});299300      const event = result.events.Transfer;301      expect(event.address).to.be.equal(collectionAddress);302      expect(event.returnValues.from).to.be.equal(caller);303      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');304      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);305    }306  });307308  itEth('Can perform approve()', async ({helper}) => {309    const owner = await helper.eth.createAccountWithBalance(donor);310    const spender = helper.eth.createAccount();311312    const collection = await helper.nft.mintCollection(minter, {});313    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});314315    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);316    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317318    {319      const result = await contract.methods.approve(spender, tokenId).send({from: owner});320321      const event = result.events.Approval;322      expect(event.address).to.be.equal(collectionAddress);323      expect(event.returnValues.owner).to.be.equal(owner);324      expect(event.returnValues.approved).to.be.equal(spender);325      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);326    }327  });328329  itEth('Can perform setApprovalForAll()', async ({helper}) => {330    const owner = await helper.eth.createAccountWithBalance(donor);331    const operator = helper.eth.createAccount();332333    const collection = await helper.nft.mintCollection(minter, {});334335    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);336    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);337338    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();339    expect(approvedBefore).to.be.equal(false);340341    {342      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});343344      expect(result.events.ApprovalForAll).to.be.like({345        address: collectionAddress,346        event: 'ApprovalForAll',347        returnValues: {348          owner,349          operator,350          approved: true,351        },352      });353354      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();355      expect(approvedAfter).to.be.equal(true);356    }357358    {359      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});360361      expect(result.events.ApprovalForAll).to.be.like({362        address: collectionAddress,363        event: 'ApprovalForAll',364        returnValues: {365          owner,366          operator,367          approved: false,368        },369      });370371      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();372      expect(approvedAfter).to.be.equal(false);373    }374  });375376  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {377    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});378379    const owner = await helper.eth.createAccountWithBalance(donor);380    const operator = await helper.eth.createAccountWithBalance(donor, 100n);381382    const token = await collection.mintToken(minter, {Ethereum: owner});383384    const address = helper.ethAddress.fromCollectionId(collection.collectionId);385    const contract = await helper.ethNativeContract.collection(address, 'nft');386387    {388      await contract.methods.setApprovalForAll(operator, true).send({from: owner});389      const ownerCross = helper.ethCrossAccount.fromAddress(owner);390      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});391      const events = result.events.Transfer;392393      expect(events).to.be.like({394        address,395        event: 'Transfer',396        returnValues: {397          from: owner,398          to: '0x0000000000000000000000000000000000000000',399          tokenId: token.tokenId.toString(),400        },401      });402    }403404    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;405  });406407  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {408    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});409410    const owner = await helper.eth.createAccountWithBalance(donor);411    const operator = await helper.eth.createAccountWithBalance(donor);412    const receiver = charlie;413414    const token = await collection.mintToken(minter, {Ethereum: owner});415416    const address = helper.ethAddress.fromCollectionId(collection.collectionId);417    const contract = await helper.ethNativeContract.collection(address, 'nft');418419    {420      await contract.methods.setApprovalForAll(operator, true).send({from: owner});421      const ownerCross = helper.ethCrossAccount.fromAddress(owner);422      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);423      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});424      const event = result.events.Transfer;425      expect(event).to.be.like({426        address: helper.ethAddress.fromCollectionId(collection.collectionId),427        event: 'Transfer',428        returnValues: {429          from: owner,430          to: helper.address.substrateToEth(receiver.address),431          tokenId: token.tokenId.toString(),432        },433      });434    }435436    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});437  });438439  itEth('Can perform burnFromCross()', async ({helper}) => {440    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});441    const ownerSub = bob;442    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);443    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);444    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);445446    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);447    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);448449    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});450    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});451452    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);453    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');454455    // Approve tokens from substrate and ethereum:456    await token1.approve(ownerSub, {Ethereum: burnerEth});457    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});458459    // can burnFromCross:460    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});461    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});462    const events1 = result1.events.Transfer;463    const events2 = result2.events.Transfer;464465    // Check events for burnFromCross (substrate and ethereum):466    [467      [events1, token1, helper.address.substrateToEth(ownerSub.address)],468      [events2, token2, ownerEth],469    ].map(burnData => {470      expect(burnData[0]).to.be.like({471        address: collectionAddress,472        event: 'Transfer',473        returnValues: {474          from: burnData[2],475          to: '0x0000000000000000000000000000000000000000',476          tokenId: burnData[1].tokenId.toString(),477        },478      });479    });480481    expect(await token1.doesExist()).to.be.false;482    expect(await token2.doesExist()).to.be.false;483  });484485  // TODO combine all approve tests in one place486  itEth('Can perform approveCross()', async ({helper}) => {487    // arrange: create accounts488    const owner = await helper.eth.createAccountWithBalance(donor, 100n);489    const ownerCross = helper.ethCrossAccount.fromAddress(owner);490    const receiverSub = charlie;491    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);492    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);493    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);494495    // arrange: create collection and tokens:496    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});497    const token1 = await collection.mintToken(minter, {Ethereum: owner});498    const token2 = await collection.mintToken(minter, {Ethereum: owner});499500    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');501502    // Can approveCross substrate and ethereum address:503    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});504    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});505    const eventSub = resultSub.events.Approval;506    const eventEth = resultEth.events.Approval;507    expect(eventSub).to.be.like({508      address: helper.ethAddress.fromCollectionId(collection.collectionId),509      event: 'Approval',510      returnValues: {511        owner,512        approved: helper.address.substrateToEth(receiverSub.address),513        tokenId: token1.tokenId.toString(),514      },515    });516    expect(eventEth).to.be.like({517      address: helper.ethAddress.fromCollectionId(collection.collectionId),518      event: 'Approval',519      returnValues: {520        owner,521        approved: receiverEth,522        tokenId: token2.tokenId.toString(),523      },524    });525526    // Substrate address can transferFrom approved tokens:527    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});528    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});529    // Ethereum address can transferFromCross approved tokens:530    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});531    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});532  });533534  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {535    const nonOwner = await helper.eth.createAccountWithBalance(donor);536    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);537    const owner = await helper.eth.createAccountWithBalance(donor);538    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});539    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');540    const token = await collection.mintToken(minter, {Ethereum: owner});541542    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');543  });544545  itEth('Can reaffirm approved address', async ({helper}) => {546    const owner = await helper.eth.createAccountWithBalance(donor, 100n);547    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);548    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);549    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);550    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);551    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});552    const token1 = await collection.mintToken(minter, {Ethereum: owner});553    const token2 = await collection.mintToken(minter, {Ethereum: owner});554    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');555556    // Can approve and reaffirm approved address:557    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});558    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});559560    // receiver1 cannot transferFrom:561    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;562    // receiver2 can transferFrom:563    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});564565    // can set approved address to self address to remove approval:566    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});567    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});568569    // receiver1 cannot transfer token anymore:570    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;571  });572573  itEth('Can perform transferFrom()', async ({helper}) => {574    const owner = await helper.eth.createAccountWithBalance(donor);575    const spender = await helper.eth.createAccountWithBalance(donor);576    const receiver = helper.eth.createAccount();577578    const collection = await helper.nft.mintCollection(minter, {});579    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});580581    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);582    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);583584    await contract.methods.approve(spender, tokenId).send({from: owner});585586    {587      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});588589      const event = result.events.Transfer;590      expect(event.address).to.be.equal(collectionAddress);591      expect(event.returnValues.from).to.be.equal(owner);592      expect(event.returnValues.to).to.be.equal(receiver);593      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);594    }595596    {597      const balance = await contract.methods.balanceOf(receiver).call();598      expect(+balance).to.equal(1);599    }600601    {602      const balance = await contract.methods.balanceOf(owner).call();603      expect(+balance).to.equal(0);604    }605  });606607  itEth('Can perform transferFromCross()', async ({helper}) => {608    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});609610    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);611    const spender = await helper.eth.createAccountWithBalance(donor);612613    const token = await collection.mintToken(minter, {Substrate: owner.address});614615    const address = helper.ethAddress.fromCollectionId(collection.collectionId);616    const contract = await helper.ethNativeContract.collection(address, 'nft');617618    await token.approve(owner, {Ethereum: spender});619620    {621      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);622      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);623      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});624      const event = result.events.Transfer;625      expect(event).to.be.like({626        address: helper.ethAddress.fromCollectionId(collection.collectionId),627        event: 'Transfer',628        returnValues: {629          from: helper.address.substrateToEth(owner.address),630          to: helper.address.substrateToEth(receiver.address),631          tokenId: token.tokenId.toString(),632        },633      });634    }635636    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});637  });638639  itEth('Can perform transfer()', async ({helper}) => {640    const collection = await helper.nft.mintCollection(minter, {});641    const owner = await helper.eth.createAccountWithBalance(donor);642    const receiver = helper.eth.createAccount();643644    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});645646    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);647    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);648649    {650      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});651652      const event = result.events.Transfer;653      expect(event.address).to.be.equal(collectionAddress);654      expect(event.returnValues.from).to.be.equal(owner);655      expect(event.returnValues.to).to.be.equal(receiver);656      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);657    }658659    {660      const balance = await contract.methods.balanceOf(owner).call();661      expect(+balance).to.equal(0);662    }663664    {665      const balance = await contract.methods.balanceOf(receiver).call();666      expect(+balance).to.equal(1);667    }668  });669670  itEth('Can perform transferCross()', async ({helper}) => {671    const collection = await helper.nft.mintCollection(minter, {});672    const owner = await helper.eth.createAccountWithBalance(donor);673    const receiverEth = await helper.eth.createAccountWithBalance(donor);674    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);675    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);676677    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});678679    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);680    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);681682    {683      // Can transferCross to ethereum address:684      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});685      // Check events:686      const event = result.events.Transfer;687      expect(event.address).to.be.equal(collectionAddress);688      expect(event.returnValues.from).to.be.equal(owner);689      expect(event.returnValues.to).to.be.equal(receiverEth);690      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);691692      // owner has balance = 0:693      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();694      expect(+ownerBalance).to.equal(0);695      // receiver owns token:696      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();697      expect(+receiverBalance).to.equal(1);698      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});699    }700701    {702      // Can transferCross to substrate address:703      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});704      // Check events:705      const event = substrateResult.events.Transfer;706      expect(event.address).to.be.equal(collectionAddress);707      expect(event.returnValues.from).to.be.equal(receiverEth);708      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));709      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);710711      // owner has balance = 0:712      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();713      expect(+ownerBalance).to.equal(0);714      // receiver owns token:715      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});716      expect(receiverBalance).to.contain(tokenId);717    }718  });719720  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {721    const sender = await helper.eth.createAccountWithBalance(donor);722    const tokenOwner = await helper.eth.createAccountWithBalance(donor);723    const receiverSub = minter;724    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);725726    const collection = await helper.nft.mintCollection(minter, {});727    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);728    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);729730    await collection.mintToken(minter, {Ethereum: sender});731    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});732733    // Cannot transferCross someone else's token:734    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;735    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;736    // Cannot transfer token if it does not exist:737    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;738  }));739});740741describe('NFT: Fees', () => {742  let donor: IKeyringPair;743  let alice: IKeyringPair;744  let bob: IKeyringPair;745  let charlie: IKeyringPair;746747  before(async function() {748    await usingEthPlaygrounds(async (helper, privateKey) => {749      donor = await privateKey({filename: __filename});750      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);751    });752  });753754  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {755    const owner = await helper.eth.createAccountWithBalance(donor);756    const spender = 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.approve(spender, tokenId).send({from: owner}));764    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));765  });766767  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {768    const owner = await helper.eth.createAccountWithBalance(donor);769    const spender = await helper.eth.createAccountWithBalance(donor);770771    const collection = await helper.nft.mintCollection(alice, {});772    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});773774    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);775776    await contract.methods.approve(spender, tokenId).send({from: owner});777778    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));779    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));780  });781782  itEth('Can perform transferFromCross()', async ({helper}) => {783    const collectionMinter = alice;784    const owner = bob;785    const receiver = charlie;786    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});787788    const spender = await helper.eth.createAccountWithBalance(donor, 100n);789790    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});791792    const address = helper.ethAddress.fromCollectionId(collection.collectionId);793    const contract = await helper.ethNativeContract.collection(address, 'nft');794795    await token.approve(owner, {Ethereum: spender});796797    {798      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);799      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);800      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});801      const event = result.events.Transfer;802      expect(event).to.be.like({803        address: helper.ethAddress.fromCollectionId(collection.collectionId),804        event: 'Transfer',805        returnValues: {806          from: helper.address.substrateToEth(owner.address),807          to: helper.address.substrateToEth(receiver.address),808          tokenId: token.tokenId.toString(),809        },810      });811    }812813    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});814  });815816  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {817    const owner = await helper.eth.createAccountWithBalance(donor);818    const receiver = helper.eth.createAccount();819820    const collection = await helper.nft.mintCollection(alice, {});821    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});822823    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);824825    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));826    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));827  });828});829830describe('NFT: Substrate calls', () => {831  let donor: IKeyringPair;832  let alice: IKeyringPair;833834  before(async function() {835    await usingEthPlaygrounds(async (helper, privateKey) => {836      donor = await privateKey({filename: __filename});837      [alice] = await helper.arrange.createAccounts([20n], donor);838    });839  });840841  itEth('Events emitted for mint()', async ({helper}) => {842    const collection = await helper.nft.mintCollection(alice, {});843    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);844    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');845846    const events: any = [];847    contract.events.allEvents((_: any, event: any) => {848      events.push(event);849    });850851    const {tokenId} = await collection.mintToken(alice);852    if (events.length == 0) await helper.wait.newBlocks(1);853    const event = events[0];854855    expect(event.event).to.be.equal('Transfer');856    expect(event.address).to.be.equal(collectionAddress);857    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');858    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));859    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());860  });861862  itEth('Events emitted for burn()', async ({helper}) => {863    const collection = await helper.nft.mintCollection(alice, {});864    const token = await collection.mintToken(alice);865866    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);867    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');868869    const events: any = [];870    contract.events.allEvents((_: any, event: any) => {871      events.push(event);872    });873874    await token.burn(alice);875    if (events.length == 0) await helper.wait.newBlocks(1);876    const event = events[0];877878    expect(event.event).to.be.equal('Transfer');879    expect(event.address).to.be.equal(collectionAddress);880    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));881    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');882    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());883  });884885  itEth('Events emitted for approve()', async ({helper}) => {886    const receiver = helper.eth.createAccount();887888    const collection = await helper.nft.mintCollection(alice, {});889    const token = await collection.mintToken(alice);890891    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);892    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');893894    const events: any = [];895    contract.events.allEvents((_: any, event: any) => {896      events.push(event);897    });898899    await token.approve(alice, {Ethereum: receiver});900    if (events.length == 0) await helper.wait.newBlocks(1);901    const event = events[0];902903    expect(event.event).to.be.equal('Approval');904    expect(event.address).to.be.equal(collectionAddress);905    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));906    expect(event.returnValues.approved).to.be.equal(receiver);907    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());908  });909910  itEth('Events emitted for transferFrom()', async ({helper}) => {911    const [bob] = await helper.arrange.createAccounts([10n], donor);912    const receiver = helper.eth.createAccount();913914    const collection = await helper.nft.mintCollection(alice, {});915    const token = await collection.mintToken(alice);916    await token.approve(alice, {Substrate: bob.address});917918    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);919    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');920921    const events: any = [];922    contract.events.allEvents((_: any, event: any) => {923      events.push(event);924    });925926    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});927928    if (events.length == 0) await helper.wait.newBlocks(1);929    const event = events[0];930931    expect(event.address).to.be.equal(collectionAddress);932    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));933    expect(event.returnValues.to).to.be.equal(receiver);934    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);935  });936937  itEth('Events emitted for transfer()', async ({helper}) => {938    const receiver = helper.eth.createAccount();939940    const collection = await helper.nft.mintCollection(alice, {});941    const token = await collection.mintToken(alice);942943    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);944    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');945946    const events: any = [];947    contract.events.allEvents((_: any, event: any) => {948      events.push(event);949    });950951    await token.transfer(alice, {Ethereum: receiver});952953    if (events.length == 0) await helper.wait.newBlocks(1);954    const event = events[0];955956    expect(event.address).to.be.equal(collectionAddress);957    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));958    expect(event.returnValues.to).to.be.equal(receiver);959    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);960  });961});962963describe('Common metadata', () => {964  let donor: IKeyringPair;965  let alice: IKeyringPair;966967  before(async function() {968    await usingEthPlaygrounds(async (helper, privateKey) => {969      donor = await privateKey({filename: __filename});970      [alice] = await helper.arrange.createAccounts([20n], donor);971    });972  });973974  itEth('Returns collection name', async ({helper}) => {975    const caller = await helper.eth.createAccountWithBalance(donor);976    const tokenPropertyPermissions = [{977      key: 'URI',978      permission: {979        mutable: true,980        collectionAdmin: true,981        tokenOwner: false,982      },983    }];984    const collection = await helper.nft.mintCollection(985      alice,986      {987        name: 'oh River',988        tokenPrefix: 'CHANGE',989        properties: [{key: 'ERC721Metadata', value: '1'}],990        tokenPropertyPermissions,991      },992    );993994    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);995    const name = await contract.methods.name().call();996    expect(name).to.equal('oh River');997  });998999  itEth('Returns symbol name', async ({helper}) => {1000    const caller = await helper.eth.createAccountWithBalance(donor);1001    const tokenPropertyPermissions = [{1002      key: 'URI',1003      permission: {1004        mutable: true,1005        collectionAdmin: true,1006        tokenOwner: false,1007      },1008    }];1009    const collection = await helper.nft.mintCollection(1010      alice,1011      {1012        name: 'oh River',1013        tokenPrefix: 'CHANGE',1014        properties: [{key: 'ERC721Metadata', value: '1'}],1015        tokenPropertyPermissions,1016      },1017    );10181019    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);1020    const symbol = await contract.methods.symbol().call();1021    expect(symbol).to.equal('CHANGE');1022  });1023});10241025describe('Negative tests', () => {1026  let donor: IKeyringPair;1027  let minter: IKeyringPair;1028  let alice: IKeyringPair;10291030  before(async function() {1031    await usingEthPlaygrounds(async (helper, privateKey) => {1032      donor = await privateKey({filename: __filename});1033      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);1034    });1035  });10361037  itEth('[negative] Cant perform burn without approval', async ({helper}) => {1038    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});10391040    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1041    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10421043    const token = await collection.mintToken(minter, {Ethereum: owner});10441045    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1046    const contract = await helper.ethNativeContract.collection(address, 'nft');10471048    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1049    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;10501051    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1052    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10531054    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;1055  });10561057  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {1058    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});1059    const receiver = alice;10601061    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1062    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10631064    const token = await collection.mintToken(minter, {Ethereum: owner});10651066    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1067    const contract = await helper.ethNativeContract.collection(address, 'nft');10681069    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1070    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);10711072    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;10731074    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1075    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10761077    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1078  });1079});
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  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});
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -19,90 +19,6 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {ITokenPropertyPermission} from '../util/playgrounds/types';
 
-describe('Refungible: Information getting', () => {
-  let donor: IKeyringPair;
-
-  before(async function() {
-    await usingEthPlaygrounds(async (helper, privateKey) => {
-      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-
-      donor = await privateKey({filename: __filename});
-    });
-  });
-
-  itEth('totalSupply', async ({helper}) => {
-    const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');
-    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
-
-    await contract.methods.mint(caller).send();
-
-    const totalSupply = await contract.methods.totalSupply().call();
-    expect(totalSupply).to.equal('1');
-  });
-
-  itEth('balanceOf', async ({helper}) => {
-    const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');
-    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
-
-    await contract.methods.mint(caller).send();
-    await contract.methods.mint(caller).send();
-    await contract.methods.mint(caller).send();
-
-    const balance = await contract.methods.balanceOf(caller).call();
-    expect(balance).to.equal('3');
-  });
-
-  itEth('ownerOf', async ({helper}) => {
-    const caller = await helper.eth.createAccountWithBalance(donor);
-    const {collectionAddress} = await helper.eth.createRFTCollection(caller, '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 owner = await contract.methods.ownerOf(tokenId).call();
-    expect(owner).to.equal(caller);
-  });
-
-  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);
-  });
-
-  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');
-  });
-});
-
 describe('Refungible: Plain calls', () => {
   let donor: IKeyringPair;
   let minter: IKeyringPair;
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -19,51 +19,6 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
 
-
-describe('Refungible token: Information getting', () => {
-  let donor: IKeyringPair;
-  let alice: IKeyringPair;
-
-  before(async function() {
-    await usingEthPlaygrounds(async (helper, privateKey) => {
-      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-
-      donor = await privateKey({filename: __filename});
-      [alice] = await helper.arrange.createAccounts([20n], donor);
-    });
-  });
-
-  itEth('totalSupply', async ({helper}) => {
-    const caller = await helper.eth.createAccountWithBalance(donor);
-    const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'});
-    const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller});
-
-    const contract = await helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, 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 collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'});
-    const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller});
-
-    const contract = await helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, 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 = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'});
-    const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller});
-
-    const contract = await helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller);
-    const decimals = await contract.methods.decimals().call();
-    expect(decimals).to.equal('0');
-  });
-});
-
 // FIXME: Need erc721 for ReFubgible.
 describe('Check ERC721 token URI for ReFungible', () => {
   let donor: IKeyringPair;
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -255,7 +255,7 @@
     }
   }
 
-  async createCollection(mode: TCollectionMode, signer: string, name: string, description: string, tokenPrefix: string, decimals = 18): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> {
+  async createCollection(mode: TCollectionMode, signer: string, name: string, description: string, tokenPrefix: string, decimals = 18, mergeDeprecated = false): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[], collection: Contract }> {
     const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
     const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(signer);
     const functionName: string = this.createCollectionMethodName(mode);
@@ -266,8 +266,9 @@
     const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
     const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
     const events = this.helper.eth.normalizeEvents(result.events);
+    const collection = await this.helper.ethNativeContract.collectionById(collectionId, mode, signer, mergeDeprecated);
 
-    return {collectionId, collectionAddress, events};
+    return {collectionId, collectionAddress, events, collection};
   }
 
   createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> {