git.delta.rocks / unique-network / refs/commits / 470ffd5ce9b5

difftreelog

source

js-packages/tests/eth/reFungible.test.ts42.1 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.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 {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';18import {expect, itEth, usingEthPlaygrounds} from '@unique/test-utils/eth/util.js';19import type {IKeyringPair} from '@polkadot/types/types';20import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js';21import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from '@unique/test-utils/eth/types.js';2223describe('Refungible: Plain calls', () => {24  let donor: IKeyringPair;25  let minter: IKeyringPair;26  let bob: IKeyringPair;27  let charlie: IKeyringPair;2829  before(async function() {30    await usingEthPlaygrounds(async (helper, privateKey) => {31      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);3233      donor = await privateKey({url: import.meta.url});34      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);35    });36  });3738  [39    'substrate' as const,40    'ethereum' as const,41  ].map(testCase => {42    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {43      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);4445      const receiverEth = helper.eth.createAccount();46      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);47      const receiverSub = bob;48      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);4950      const properties = Array(5).fill(0).map((_, i) => ({key: `key_${i}`, value: Buffer.from(`value_${i}`)}));51      const permissions: ITokenPropertyPermission[] = properties.map(p => ({key: p.key, permission: {52        tokenOwner: false,53        collectionAdmin: true,54        mutable: false}}));555657      const collection = await helper.rft.mintCollection(minter, {58        tokenPrefix: 'ethp',59        tokenPropertyPermissions: permissions,60      });61      await collection.addAdmin(minter, {Ethereum: collectionAdmin});6263      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);64      const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', collectionAdmin, true);65      let expectedTokenId = await contract.methods.nextTokenId().call();66      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();67      let tokenId = result.events.Transfer.returnValues.tokenId;68      expect(tokenId).to.be.equal(expectedTokenId);6970      let event = result.events.Transfer;71      expect(event.address).to.be.equal(collectionAddress);72      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');73      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));74      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);7576      expectedTokenId = await contract.methods.nextTokenId().call();77      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();78      event = result.events.Transfer;79      expect(event.address).to.be.equal(collectionAddress);80      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');81      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));82      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);8384      tokenId = result.events.Transfer.returnValues.tokenId;8586      expect(tokenId).to.be.equal(expectedTokenId);8788      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties89        .map(p => helper.ethProperty.property(p.key, p.value.toString())));9091      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))92        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});93    });94  });9596  itEth.skip('Can perform mintBulk()', async ({helper}) => {97    const owner = await helper.eth.createAccountWithBalance(donor);98    const receiver = helper.eth.createAccount();99    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');100    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);101102    {103      const nextTokenId = await contract.methods.nextTokenId().call();104      expect(nextTokenId).to.be.equal('1');105      const result = await contract.methods.mintBulkWithTokenURI(106        receiver,107        [108          [nextTokenId, 'Test URI 0'],109          [+nextTokenId + 1, 'Test URI 1'],110          [+nextTokenId + 2, 'Test URI 2'],111        ],112      ).send();113114      const events = result.events.Transfer;115      for(let i = 0; i < 2; i++) {116        const event = events[i];117        expect(event.address).to.equal(collectionAddress);118        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');119        expect(event.returnValues.to).to.equal(receiver);120        expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));121      }122123      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');124      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');125      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');126    }127  });128129  itEth('Can perform mintBulkCross() with multiple tokens', async ({helper}) => {130    const caller = await helper.eth.createAccountWithBalance(donor);131    const callerCross = helper.ethCrossAccount.fromAddress(caller);132    const receiver = helper.eth.createAccount();133    const receiverCross = helper.ethCrossAccount.fromAddress(receiver);134135    const permissions = [136      {code: TokenPermissionField.Mutable, value: true},137      {code: TokenPermissionField.TokenOwner, value: true},138      {code: TokenPermissionField.CollectionAdmin, value: true},139    ];140    const {collectionAddress} = await helper.eth.createCollection(141      caller,142      {143        ...CREATE_COLLECTION_DATA_DEFAULTS,144        name: 'A',145        description: 'B',146        tokenPrefix: 'C',147        collectionMode: 'rft',148        adminList: [callerCross],149        tokenPropertyPermissions: [150          {key: 'key_0_0', permissions},151          {key: 'key_1_0', permissions},152          {key: 'key_1_1', permissions},153          {key: 'key_2_0', permissions},154          {key: 'key_2_1', permissions},155          {key: 'key_2_2', permissions},156        ],157      },158    ).send();159160    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);161    const nextTokenId = await contract.methods.nextTokenId().call();162    expect(nextTokenId).to.be.equal('1');163    const result = await contract.methods.mintBulkCross([164      {165        owners: [{166          owner: receiverCross,167          pieces: 1,168        }],169        properties: [170          {key: 'key_0_0', value: Buffer.from('value_0_0')},171        ],172      },173      {174        owners: [{175          owner: receiverCross,176          pieces: 2,177        }],178        properties: [179          {key: 'key_1_0', value: Buffer.from('value_1_0')},180          {key: 'key_1_1', value: Buffer.from('value_1_1')},181        ],182      },183      {184        owners: [{185          owner: receiverCross,186          pieces: 1,187        }],188        properties: [189          {key: 'key_2_0', value: Buffer.from('value_2_0')},190          {key: 'key_2_1', value: Buffer.from('value_2_1')},191          {key: 'key_2_2', value: Buffer.from('value_2_2')},192        ],193      },194    ]).send({from: caller});195    const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);196    const bulkSize = 3;197    for(let i = 0; i < bulkSize; i++) {198      const event = events[i];199      expect(event.address).to.equal(collectionAddress);200      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');201      expect(event.returnValues.to).to.equal(receiver);202      expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);203    }204205    const properties = [206      await contract.methods.properties(+nextTokenId, []).call(),207      await contract.methods.properties(+nextTokenId + 1, []).call(),208      await contract.methods.properties(+nextTokenId + 2, []).call(),209    ];210    expect(properties).to.be.deep.equal([211      [212        ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')],213      ],214      [215        ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')],216        ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')],217      ],218      [219        ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],220        ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],221        ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],222      ],223    ]);224  });225226  itEth('Can perform mintBulkCross() with multiple owners', async ({helper}) => {227    const caller = await helper.eth.createAccountWithBalance(donor);228    const callerCross = helper.ethCrossAccount.fromAddress(caller);229    const receiver = helper.eth.createAccount();230    const receiverCross = helper.ethCrossAccount.fromAddress(receiver);231    const receiver2 = helper.eth.createAccount();232    const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2);233234    const permissions = [235      {code: TokenPermissionField.Mutable, value: true},236      {code: TokenPermissionField.TokenOwner, value: true},237      {code: TokenPermissionField.CollectionAdmin, value: true},238    ];239    const {collectionAddress} = await helper.eth.createCollection(240      caller,241      {242        ...CREATE_COLLECTION_DATA_DEFAULTS,243        name: 'A',244        description: 'B',245        tokenPrefix: 'C',246        collectionMode: 'rft',247        adminList: [callerCross],248        tokenPropertyPermissions: [249          {key: 'key_2_0', permissions},250          {key: 'key_2_1', permissions},251          {key: 'key_2_2', permissions},252        ],253      },254    ).send();255256    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);257    const nextTokenId = await contract.methods.nextTokenId().call();258    expect(nextTokenId).to.be.equal('1');259    const result = await contract.methods.mintBulkCross([{260      owners: [261        {262          owner: receiverCross,263          pieces: 1,264        },265        {266          owner: receiver2Cross,267          pieces: 2,268        },269      ],270      properties: [271        {key: 'key_2_0', value: Buffer.from('value_2_0')},272        {key: 'key_2_1', value: Buffer.from('value_2_1')},273        {key: 'key_2_2', value: Buffer.from('value_2_2')},274      ],275    }]).send({from: caller});276    const event = result.events.Transfer;277    expect(event.address).to.equal(collectionAddress);278    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');279    expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');280    expect(event.returnValues.tokenId).to.equal(`${+nextTokenId}`);281282    const properties = [283      await contract.methods.properties(+nextTokenId, []).call(),284    ];285    expect(properties).to.be.deep.equal([[286      ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],287      ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],288      ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],289    ]]);290  });291292  itEth('Can perform setApprovalForAll()', async ({helper}) => {293    const owner = await helper.eth.createAccountWithBalance(donor);294    const operator = helper.eth.createAccount();295296    const collection = await helper.rft.mintCollection(minter, {});297298    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);299    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);300301    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();302    expect(approvedBefore).to.be.equal(false);303304    {305      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});306307      expect(result.events.ApprovalForAll).to.be.like({308        address: collectionAddress,309        event: 'ApprovalForAll',310        returnValues: {311          owner,312          operator,313          approved: true,314        },315      });316317      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();318      expect(approvedAfter).to.be.equal(true);319    }320321    {322      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});323324      expect(result.events.ApprovalForAll).to.be.like({325        address: collectionAddress,326        event: 'ApprovalForAll',327        returnValues: {328          owner,329          operator,330          approved: false,331        },332      });333334      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();335      expect(approvedAfter).to.be.equal(false);336    }337  });338339  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {340    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});341342    const owner = await helper.eth.createAccountWithBalance(donor);343    const operator = await helper.eth.createAccountWithBalance(donor);344345    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});346347    const address = helper.ethAddress.fromCollectionId(collection.collectionId);348    const contract = await helper.ethNativeContract.collection(address, 'rft');349350    {351      await contract.methods.setApprovalForAll(operator, true).send({from: owner});352      const ownerCross = helper.ethCrossAccount.fromAddress(owner);353      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});354      const events = result.events.Transfer;355356      expect(events).to.be.like({357        address,358        event: 'Transfer',359        returnValues: {360          from: owner,361          to: '0x0000000000000000000000000000000000000000',362          tokenId: token.tokenId.toString(),363        },364      });365    }366  });367368  itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {369    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});370371    const owner = await helper.eth.createAccountWithBalance(donor);372    const operator = await helper.eth.createAccountWithBalance(donor);373374    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});375376    const address = helper.ethAddress.fromCollectionId(collection.collectionId);377    const contract = await helper.ethNativeContract.collection(address, 'rft');378379    const rftToken = await helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner, true);380381    {382      await rftToken.methods.approve(operator, 15n).send({from: owner});383      await contract.methods.setApprovalForAll(operator, true).send({from: owner});384      await rftToken.methods.burnFrom(owner, 10n).send({from: operator});385    }386    {387      const allowance = await rftToken.methods.allowance(owner, operator).call();388      expect(+allowance).to.be.equal(5);389    }390    {391      const ownerCross = helper.ethCrossAccount.fromAddress(owner);392      const operatorCross = helper.ethCrossAccount.fromAddress(operator);393      const allowance = await rftToken.methods.allowanceCross(ownerCross, operatorCross).call();394      expect(+allowance).to.equal(5);395    }396  });397398  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {399    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});400401    const owner = await helper.eth.createAccountWithBalance(donor);402    const operator = await helper.eth.createAccountWithBalance(donor);403    const receiver = charlie;404405    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});406407    const address = helper.ethAddress.fromCollectionId(collection.collectionId);408    const contract = await helper.ethNativeContract.collection(address, 'rft');409410    {411      await contract.methods.setApprovalForAll(operator, true).send({from: owner});412      const ownerCross = helper.ethCrossAccount.fromAddress(owner);413      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);414      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});415      const event = result.events.Transfer;416      expect(event).to.be.like({417        address: helper.ethAddress.fromCollectionId(collection.collectionId),418        event: 'Transfer',419        returnValues: {420          from: owner,421          to: helper.address.substrateToEth(receiver.address),422          tokenId: token.tokenId.toString(),423        },424      });425    }426427    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);428  });429430  itEth('Can perform burn()', async ({helper}) => {431    const caller = await helper.eth.createAccountWithBalance(donor);432    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');433    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);434435    const result = await contract.methods.mint(caller).send();436    const tokenId = result.events.Transfer.returnValues.tokenId;437    {438      const result = await contract.methods.burn(tokenId).send();439      const event = result.events.Transfer;440      expect(event.address).to.equal(collectionAddress);441      expect(event.returnValues.from).to.equal(caller);442      expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');443      expect(event.returnValues.tokenId).to.equal(tokenId.toString());444    }445  });446447  itEth('Can perform transferFrom()', async ({helper}) => {448    const caller = await helper.eth.createAccountWithBalance(donor);449    const receiver = helper.eth.createAccount();450    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');451    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);452453    const result = await contract.methods.mint(caller).send();454    const tokenId = result.events.Transfer.returnValues.tokenId;455456    const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);457458    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, caller);459    await tokenContract.methods.repartition(15).send();460461    {462      const tokenEvents: any = [];463      tokenContract.events.allEvents((_: any, event: any) => {464        tokenEvents.push(event);465      });466      const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();467      if(tokenEvents.length == 0) await helper.wait.newBlocks(1);468469      let event = result.events.Transfer;470      expect(event.address).to.equal(collectionAddress);471      expect(event.returnValues.from).to.equal(caller);472      expect(event.returnValues.to).to.equal(receiver);473      expect(event.returnValues.tokenId).to.equal(tokenId.toString());474475      event = tokenEvents[0];476      expect(event.address).to.equal(tokenAddress);477      expect(event.returnValues.from).to.equal(caller);478      expect(event.returnValues.to).to.equal(receiver);479      expect(event.returnValues.value).to.equal('15');480    }481482    {483      const balance = await contract.methods.balanceOf(receiver).call();484      expect(+balance).to.equal(1);485    }486487    {488      const balance = await contract.methods.balanceOf(caller).call();489      expect(+balance).to.equal(0);490    }491  });492493  // Soft-deprecated494  itEth('Can perform burnFrom()', async ({helper}) => {495    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});496497    const owner = await helper.eth.createAccountWithBalance(donor);498    const spender = await helper.eth.createAccountWithBalance(donor);499500    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});501502    const address = helper.ethAddress.fromCollectionId(collection.collectionId);503    const contract = await helper.ethNativeContract.collection(address, 'rft', spender, true);504505    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);506    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner);507    await tokenContract.methods.repartition(15).send();508    await tokenContract.methods.approve(spender, 15).send();509510    {511      const result = await contract.methods.burnFrom(owner, token.tokenId).send();512      const event = result.events.Transfer;513      expect(event).to.be.like({514        address: helper.ethAddress.fromCollectionId(collection.collectionId),515        event: 'Transfer',516        returnValues: {517          from: owner,518          to: '0x0000000000000000000000000000000000000000',519          tokenId: token.tokenId.toString(),520        },521      });522    }523524    expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);525  });526527  itEth('Can perform burnFromCross()', async ({helper}) => {528    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});529530    const owner = bob;531    const spender = await helper.eth.createAccountWithBalance(donor);532533    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});534535    const address = helper.ethAddress.fromCollectionId(collection.collectionId);536    const contract = await helper.ethNativeContract.collection(address, 'rft');537538    await token.repartition(owner, 15n);539    await token.approve(owner, {Ethereum: spender}, 15n);540541    {542      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);543      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});544      const event = result.events.Transfer;545      expect(event).to.be.like({546        address: helper.ethAddress.fromCollectionId(collection.collectionId),547        event: 'Transfer',548        returnValues: {549          from: helper.address.substrateToEth(owner.address),550          to: '0x0000000000000000000000000000000000000000',551          tokenId: token.tokenId.toString(),552        },553      });554    }555556    expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);557  });558559  itEth('Can perform transferFromCross()', async ({helper}) => {560    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});561562    const owner = bob;563    const spender = await helper.eth.createAccountWithBalance(donor);564    const receiver = charlie;565566    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});567568    const address = helper.ethAddress.fromCollectionId(collection.collectionId);569    const contract = await helper.ethNativeContract.collection(address, 'rft');570571    await token.repartition(owner, 15n);572    await token.approve(owner, {Ethereum: spender}, 15n);573574    {575      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);576      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);577      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});578      const event = result.events.Transfer;579      expect(event).to.be.like({580        address: helper.ethAddress.fromCollectionId(collection.collectionId),581        event: 'Transfer',582        returnValues: {583          from: helper.address.substrateToEth(owner.address),584          to: helper.address.substrateToEth(receiver.address),585          tokenId: token.tokenId.toString(),586        },587      });588    }589590    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);591  });592593  itEth('Can perform transfer()', async ({helper}) => {594    const caller = await helper.eth.createAccountWithBalance(donor);595    const receiver = helper.eth.createAccount();596    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');597    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);598599    const result = await contract.methods.mint(caller).send();600    const tokenId = result.events.Transfer.returnValues.tokenId;601602    {603      const result = await contract.methods.transfer(receiver, tokenId).send();604605      const event = result.events.Transfer;606      expect(event.address).to.equal(collectionAddress);607      expect(event.returnValues.from).to.equal(caller);608      expect(event.returnValues.to).to.equal(receiver);609      expect(event.returnValues.tokenId).to.equal(tokenId.toString());610    }611612    {613      const balance = await contract.methods.balanceOf(caller).call();614      expect(+balance).to.equal(0);615    }616617    {618      const balance = await contract.methods.balanceOf(receiver).call();619      expect(+balance).to.equal(1);620    }621  });622623  itEth('Can perform transferCross()', async ({helper}) => {624    const sender = await helper.eth.createAccountWithBalance(donor);625    const receiverEth = await helper.eth.createAccountWithBalance(donor);626    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);627    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);628629    const collection = await helper.rft.mintCollection(minter, {});630    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);631    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);632633    const token = await collection.mintToken(minter, 50n, {Ethereum: sender});634635    {636      // Can transferCross to ethereum address:637      const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});638      // Check events:639      const event = result.events.Transfer;640      expect(event.address).to.equal(collectionAddress);641      expect(event.returnValues.from).to.equal(sender);642      expect(event.returnValues.to).to.equal(receiverEth);643      expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());644      // Sender's balance decreased:645      const senderBalance = await collectionEvm.methods.balanceOf(sender).call();646      expect(+senderBalance).to.equal(0);647      expect(await token.getBalance({Ethereum: sender})).to.eq(0n);648      // Receiver's balance increased:649      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();650      expect(+receiverBalance).to.equal(1);651      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);652    }653654    {655      // Can transferCross to substrate address:656      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});657      // Check events:658      const event = substrateResult.events.Transfer;659      expect(event.address).to.be.equal(collectionAddress);660      expect(event.returnValues.from).to.be.equal(receiverEth);661      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));662      expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);663      // Sender's balance decreased:664      const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();665      expect(+senderBalance).to.equal(0);666      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);667      // Receiver's balance increased:668      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});669      expect(receiverBalance).to.contain(token.tokenId);670      expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);671    }672  });673674  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {675    const sender = await helper.eth.createAccountWithBalance(donor);676    const tokenOwner = await helper.eth.createAccountWithBalance(donor);677    const receiverSub = minter;678    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);679680    const collection = await helper.rft.mintCollection(minter, {});681    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);682    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);683684    await collection.mintToken(minter, 50n, {Ethereum: sender});685    const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});686687    // Cannot transferCross someone else's token:688    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;689    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;690    // Cannot transfer token if it does not exist:691    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;692  }));693694  itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {695    const caller = await helper.eth.createAccountWithBalance(donor);696    const receiver = helper.eth.createAccount();697    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');698    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);699700    const result = await contract.methods.mint(caller).send();701    const tokenId = result.events.Transfer.returnValues.tokenId;702703    const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);704705    await tokenContract.methods.repartition(2).send();706    await tokenContract.methods.transfer(receiver, 1).send();707708    const events: any = [];709    contract.events.allEvents((_: any, event: any) => {710      events.push(event);711    });712713    await tokenContract.methods.transfer(receiver, 1).send();714    if(events.length == 0) await helper.wait.newBlocks(1);715    const event = events[0];716717    expect(event.address).to.equal(collectionAddress);718    expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');719    expect(event.returnValues.to).to.equal(receiver);720    expect(event.returnValues.tokenId).to.equal(tokenId.toString());721  });722723  itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {724    const caller = await helper.eth.createAccountWithBalance(donor);725    const receiver = helper.eth.createAccount();726    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');727    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);728729    const result = await contract.methods.mint(caller).send();730    const tokenId = result.events.Transfer.returnValues.tokenId;731732    const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);733734    await tokenContract.methods.repartition(2).send();735736    const events: any = [];737    contract.events.allEvents((_: any, event: any) => {738      events.push(event);739    });740741    await tokenContract.methods.transfer(receiver, 1).send();742    if(events.length == 0) await helper.wait.newBlocks(1);743    const event = events[0];744745    expect(event.address).to.equal(collectionAddress);746    expect(event.returnValues.from).to.equal(caller);747    expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');748    expect(event.returnValues.tokenId).to.equal(tokenId.toString());749  });750751  itEth('Check balanceOfCross()', async ({helper}) => {752    const collection = await helper.rft.mintCollection(minter, {});753    const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);754    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);755    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner.eth);756757    expect(await collectionEvm.methods.balanceOfCross(owner).call({from: owner.eth})).to.be.eq('0');758759    for(let i = 1n; i < 10n; i++) {760      await collection.mintToken(minter, 100n, {Ethereum: owner.eth});761      expect(await collectionEvm.methods.balanceOfCross(owner).call({from: owner.eth})).to.be.eq(i.toString());762    }763  });764765  itEth('Check ownerOfCross()', async ({helper}) => {766    const collection = await helper.rft.mintCollection(minter, {});767    let owner = await helper.ethCrossAccount.createAccountWithBalance(donor);768    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);769    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner.eth);770    const {tokenId} = await collection.mintToken(minter, 100n,{Ethereum: owner.eth});771772    for(let i = 1n; i < 10n; i++) {773      const ownerCross = await collectionEvm.methods.ownerOfCross(tokenId).call({from: owner.eth});774      expect(ownerCross.eth).to.be.eq(owner.eth);775      expect(ownerCross.sub).to.be.eq(owner.sub);776777      const newOwner = await helper.ethCrossAccount.createAccountWithBalance(donor);778      await collectionEvm.methods.transferCross(newOwner, tokenId).send({from: owner.eth});779      owner = newOwner;780    }781782    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId);783    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner.eth, true);784    const newOwner = await helper.ethCrossAccount.createAccountWithBalance(donor);785    await tokenContract.methods.transferCross(newOwner, 50).send({from: owner.eth});786    const ownerCross = await collectionEvm.methods.ownerOfCross(tokenId).call({from: owner.eth});787    expect(ownerCross.eth.toUpperCase()).to.be.eq('0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF');788    expect(ownerCross.sub).to.be.eq('0');789  });790});791792describe('RFT: Fees', () => {793  let donor: IKeyringPair;794795  before(async function() {796    await usingEthPlaygrounds(async (helper, privateKey) => {797      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);798799      donor = await privateKey({url: import.meta.url});800    });801  });802803  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {804    const caller = await helper.eth.createAccountWithBalance(donor);805    const receiver = helper.eth.createAccount();806    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');807    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);808809    const result = await contract.methods.mint(caller).send();810    const tokenId = result.events.Transfer.returnValues.tokenId;811812    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());813    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));814    expect(cost > 0n);815  });816817  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {818    const caller = await helper.eth.createAccountWithBalance(donor);819    const receiver = helper.eth.createAccount();820    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');821    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);822823    const result = await contract.methods.mint(caller).send();824    const tokenId = result.events.Transfer.returnValues.tokenId;825826    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());827    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));828    expect(cost > 0n);829  });830});831832describe('Common metadata', () => {833  let donor: IKeyringPair;834  let alice: IKeyringPair;835836  before(async function() {837    await usingEthPlaygrounds(async (helper, privateKey) => {838      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);839840      donor = await privateKey({url: import.meta.url});841      [alice] = await helper.arrange.createAccounts([1000n], donor);842    });843  });844845  itEth('Returns collection name', async ({helper}) => {846    const caller = helper.eth.createAccount();847    const tokenPropertyPermissions = [{848      key: 'URI',849      permission: {850        mutable: true,851        collectionAdmin: true,852        tokenOwner: false,853      },854    }];855    const collection = await helper.rft.mintCollection(856      alice,857      {858        name: 'Leviathan',859        tokenPrefix: '11',860        properties: [{key: 'ERC721Metadata', value: '1'}],861        tokenPropertyPermissions,862      },863    );864865    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);866    const name = await contract.methods.name().call();867    expect(name).to.equal('Leviathan');868  });869870  itEth('Returns symbol name', async ({helper}) => {871    const caller = await helper.eth.createAccountWithBalance(donor);872    const tokenPropertyPermissions = [{873      key: 'URI',874      permission: {875        mutable: true,876        collectionAdmin: true,877        tokenOwner: false,878      },879    }];880    const {collectionId} = await helper.rft.mintCollection(881      alice,882      {883        name: 'Leviathan',884        tokenPrefix: '12',885        properties: [{key: 'ERC721Metadata', value: '1'}],886        tokenPropertyPermissions,887      },888    );889890    const contract = await helper.ethNativeContract.collectionById(collectionId, 'rft', caller);891    const symbol = await contract.methods.symbol().call();892    expect(symbol).to.equal('12');893  });894});895896describe('Negative tests', () => {897  let donor: IKeyringPair;898  let minter: IKeyringPair;899  let alice: IKeyringPair;900901  before(async function() {902    await usingEthPlaygrounds(async (helper, privateKey) => {903      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);904905      donor = await privateKey({url: import.meta.url});906      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);907    });908  });909910  itEth('[negative] Cant perform burn without approval', async ({helper}) => {911    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});912913    const owner = await helper.eth.createAccountWithBalance(donor);914    const spender = await helper.eth.createAccountWithBalance(donor);915916    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});917918    const address = helper.ethAddress.fromCollectionId(collection.collectionId);919    const contract = await helper.ethNativeContract.collection(address, 'rft');920921    const ownerCross = helper.ethCrossAccount.fromAddress(owner);922923    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;924925    await contract.methods.setApprovalForAll(spender, true).send({from: owner});926    await contract.methods.setApprovalForAll(spender, false).send({from: owner});927928    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;929  });930931  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {932    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});933    const owner = await helper.eth.createAccountWithBalance(donor);934    const receiver = alice;935936    const spender = await helper.eth.createAccountWithBalance(donor);937938    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});939940    const address = helper.ethAddress.fromCollectionId(collection.collectionId);941    const contract = await helper.ethNativeContract.collection(address, 'rft');942943    const ownerCross = helper.ethCrossAccount.fromAddress(owner);944    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);945946    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;947948    await contract.methods.setApprovalForAll(spender, true).send({from: owner});949    await contract.methods.setApprovalForAll(spender, false).send({from: owner});950951    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;952  });953954  itEth('[negative] Can perform mintBulkCross() with multiple owners and multiple tokens', async ({helper}) => {955    const caller = await helper.eth.createAccountWithBalance(donor);956    const callerCross = helper.ethCrossAccount.fromAddress(caller);957    const receiver = helper.eth.createAccount();958    const receiverCross = helper.ethCrossAccount.fromAddress(receiver);959    const receiver2 = helper.eth.createAccount();960    const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2);961962    const permissions = [963      {code: TokenPermissionField.Mutable, value: true},964      {code: TokenPermissionField.TokenOwner, value: true},965      {code: TokenPermissionField.CollectionAdmin, value: true},966    ];967    const {collectionAddress} = await helper.eth.createCollection(968      caller,969      {970        ...CREATE_COLLECTION_DATA_DEFAULTS,971        name: 'A',972        description: 'B',973        tokenPrefix: 'C',974        collectionMode: 'rft',975        adminList: [callerCross],976        tokenPropertyPermissions: [977          {key: 'key_0_0', permissions},978          {key: 'key_2_0', permissions},979          {key: 'key_2_1', permissions},980          {key: 'key_2_2', permissions},981        ],982      },983    ).send();984985    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);986    const nextTokenId = await contract.methods.nextTokenId().call();987    expect(nextTokenId).to.be.equal('1');988    const createData = [989      {990        owners: [{991          owner: receiverCross,992          pieces: 1,993        }],994        properties: [995          {key: 'key_0_0', value: Buffer.from('value_0_0')},996        ],997      },998      {999        owners: [1000          {1001            owner: receiverCross,1002            pieces: 1,1003          },1004          {1005            owner: receiver2Cross,1006            pieces: 2,1007          },1008        ],1009        properties: [1010          {key: 'key_2_0', value: Buffer.from('value_2_0')},1011          {key: 'key_2_1', value: Buffer.from('value_2_1')},1012          {key: 'key_2_2', value: Buffer.from('value_2_2')},1013        ],1014      },1015    ];10161017    await expect(contract.methods.mintBulkCross(createData).call({from: caller})).to.be.rejectedWith('creation of multiple tokens supported only if they have single owner each');1018  });1019});