git.delta.rocks / unique-network / refs/commits / 752e2b099754

difftreelog

fix update weighting for mintBulkCross and forbid multiple owners + multipe tokens

Grigoriy Simonov2023-09-22parent: #0c121c7.patch.diff
in: master

2 files changed

modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -1041,7 +1041,11 @@
 
 	/// @notice Function to mint a token.
 	/// @param tokenProperties Properties of minted token
-	#[weight(<SelfWeightOf<T>>::create_multiple_items(token_properties.len() as u32) + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]
+	#[weight(if token_properties.len() == 1 {
+		<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_properties.iter().next().unwrap().owners.len() as u32)
+	} else {
+		<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(token_properties.len() as u32)
+	} + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]
 	fn mint_bulk_cross(
 		&mut self,
 		caller: Caller,
@@ -1051,9 +1055,17 @@
 		let budget = self
 			.recorder
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+		let has_multiple_tokens = token_properties.len() > 1;
 
 		let mut create_rft_data = Vec::with_capacity(token_properties.len());
 		for MintTokenData { owners, properties } in token_properties {
+			let has_multiple_owners = owners.len() > 1;
+			if has_multiple_tokens & has_multiple_owners {
+				return Err(
+					"creation of multiple tokens supported only if they have single owner each"
+						.into(),
+				);
+			}
 			let users: BoundedBTreeMap<_, _, _> = owners
 				.into_iter()
 				.map(|data| Ok((data.owner.into_sub_cross_account::<T>()?, data.pieces)))
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
before · tests/src/eth/reFungible.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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';20import {ITokenPropertyPermission} from '../util/playgrounds/types';21import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types';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()', 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);134    const receiver2 = helper.eth.createAccount();135    const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2);136137    const permissions = [138      {code: TokenPermissionField.Mutable, value: true},139      {code: TokenPermissionField.TokenOwner, value: true},140      {code: TokenPermissionField.CollectionAdmin, value: true},141    ];142    const {collectionAddress} = await helper.eth.createCollection(143      caller,144      {145        ...CREATE_COLLECTION_DATA_DEFAULTS,146        name: 'A',147        description: 'B',148        tokenPrefix: 'C',149        collectionMode: 'rft',150        adminList: [callerCross],151        tokenPropertyPermissions: [152          {key: 'key_0_0', permissions},153          {key: 'key_1_0', permissions},154          {key: 'key_1_1', permissions},155          {key: 'key_2_0', permissions},156          {key: 'key_2_1', permissions},157          {key: 'key_2_2', permissions},158        ],159      },160    ).send();161162    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);163    {164      const nextTokenId = await contract.methods.nextTokenId().call();165      expect(nextTokenId).to.be.equal('1');166      const result = await contract.methods.mintBulkCross([167        {168          owners: [{169            owner: receiverCross,170            pieces: 1,171          }],172          properties: [173            {key: 'key_0_0', value: Buffer.from('value_0_0')},174          ],175        },176        {177          owners: [{178            owner: receiverCross,179            pieces: 2,180          }],181          properties: [182            {key: 'key_1_0', value: Buffer.from('value_1_0')},183            {key: 'key_1_1', value: Buffer.from('value_1_1')},184          ],185        },186        {187          owners: [188            {189              owner: receiverCross,190              pieces: 1,191            },192            {193              owner: receiver2Cross,194              pieces: 2,195            },196          ],197          properties: [198            {key: 'key_2_0', value: Buffer.from('value_2_0')},199            {key: 'key_2_1', value: Buffer.from('value_2_1')},200            {key: 'key_2_2', value: Buffer.from('value_2_2')},201          ],202        },203      ]).send({from: caller});204      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);205      const bulkSize = 3;206      for(let i = 0; i < bulkSize; i++) {207        const event = events[i];208        expect(event.address).to.equal(collectionAddress);209        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');210        if(i == 0 || i == 1)211          expect(event.returnValues.to).to.equal(receiver);212        else213          expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');214215        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);216      }217218      const properties = [219        await contract.methods.properties(+nextTokenId, []).call(),220        await contract.methods.properties(+nextTokenId + 1, []).call(),221        await contract.methods.properties(+nextTokenId + 2, []).call(),222      ];223      expect(properties).to.be.deep.equal([224        [225          ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')],226        ],227        [228          ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')],229          ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')],230        ],231        [232          ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],233          ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],234          ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],235        ],236      ]);237    }238  });239240  itEth('Can perform setApprovalForAll()', async ({helper}) => {241    const owner = await helper.eth.createAccountWithBalance(donor);242    const operator = helper.eth.createAccount();243244    const collection = await helper.rft.mintCollection(minter, {});245246    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);247    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);248249    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();250    expect(approvedBefore).to.be.equal(false);251252    {253      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});254255      expect(result.events.ApprovalForAll).to.be.like({256        address: collectionAddress,257        event: 'ApprovalForAll',258        returnValues: {259          owner,260          operator,261          approved: true,262        },263      });264265      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();266      expect(approvedAfter).to.be.equal(true);267    }268269    {270      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});271272      expect(result.events.ApprovalForAll).to.be.like({273        address: collectionAddress,274        event: 'ApprovalForAll',275        returnValues: {276          owner,277          operator,278          approved: false,279        },280      });281282      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();283      expect(approvedAfter).to.be.equal(false);284    }285  });286287  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {288    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});289290    const owner = await helper.eth.createAccountWithBalance(donor);291    const operator = await helper.eth.createAccountWithBalance(donor);292293    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});294295    const address = helper.ethAddress.fromCollectionId(collection.collectionId);296    const contract = await helper.ethNativeContract.collection(address, 'rft');297298    {299      await contract.methods.setApprovalForAll(operator, true).send({from: owner});300      const ownerCross = helper.ethCrossAccount.fromAddress(owner);301      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});302      const events = result.events.Transfer;303304      expect(events).to.be.like({305        address,306        event: 'Transfer',307        returnValues: {308          from: owner,309          to: '0x0000000000000000000000000000000000000000',310          tokenId: token.tokenId.toString(),311        },312      });313    }314  });315316  itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {317    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});318319    const owner = await helper.eth.createAccountWithBalance(donor);320    const operator = await helper.eth.createAccountWithBalance(donor);321322    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});323324    const address = helper.ethAddress.fromCollectionId(collection.collectionId);325    const contract = await helper.ethNativeContract.collection(address, 'rft');326327    const rftToken = await helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner, true);328329    {330      await rftToken.methods.approve(operator, 15n).send({from: owner});331      await contract.methods.setApprovalForAll(operator, true).send({from: owner});332      await rftToken.methods.burnFrom(owner, 10n).send({from: operator});333    }334    {335      const allowance = await rftToken.methods.allowance(owner, operator).call();336      expect(+allowance).to.be.equal(5);337    }338    {339      const ownerCross = helper.ethCrossAccount.fromAddress(owner);340      const operatorCross = helper.ethCrossAccount.fromAddress(operator);341      const allowance = await rftToken.methods.allowanceCross(ownerCross, operatorCross).call();342      expect(+allowance).to.equal(5);343    }344  });345346  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {347    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});348349    const owner = await helper.eth.createAccountWithBalance(donor);350    const operator = await helper.eth.createAccountWithBalance(donor);351    const receiver = charlie;352353    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});354355    const address = helper.ethAddress.fromCollectionId(collection.collectionId);356    const contract = await helper.ethNativeContract.collection(address, 'rft');357358    {359      await contract.methods.setApprovalForAll(operator, true).send({from: owner});360      const ownerCross = helper.ethCrossAccount.fromAddress(owner);361      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);362      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});363      const event = result.events.Transfer;364      expect(event).to.be.like({365        address: helper.ethAddress.fromCollectionId(collection.collectionId),366        event: 'Transfer',367        returnValues: {368          from: owner,369          to: helper.address.substrateToEth(receiver.address),370          tokenId: token.tokenId.toString(),371        },372      });373    }374375    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);376  });377378  itEth('Can perform burn()', async ({helper}) => {379    const caller = await helper.eth.createAccountWithBalance(donor);380    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');381    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);382383    const result = await contract.methods.mint(caller).send();384    const tokenId = result.events.Transfer.returnValues.tokenId;385    {386      const result = await contract.methods.burn(tokenId).send();387      const event = result.events.Transfer;388      expect(event.address).to.equal(collectionAddress);389      expect(event.returnValues.from).to.equal(caller);390      expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');391      expect(event.returnValues.tokenId).to.equal(tokenId.toString());392    }393  });394395  itEth('Can perform transferFrom()', async ({helper}) => {396    const caller = await helper.eth.createAccountWithBalance(donor);397    const receiver = helper.eth.createAccount();398    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');399    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);400401    const result = await contract.methods.mint(caller).send();402    const tokenId = result.events.Transfer.returnValues.tokenId;403404    const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);405406    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, caller);407    await tokenContract.methods.repartition(15).send();408409    {410      const tokenEvents: any = [];411      tokenContract.events.allEvents((_: any, event: any) => {412        tokenEvents.push(event);413      });414      const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();415      if(tokenEvents.length == 0) await helper.wait.newBlocks(1);416417      let event = result.events.Transfer;418      expect(event.address).to.equal(collectionAddress);419      expect(event.returnValues.from).to.equal(caller);420      expect(event.returnValues.to).to.equal(receiver);421      expect(event.returnValues.tokenId).to.equal(tokenId.toString());422423      event = tokenEvents[0];424      expect(event.address).to.equal(tokenAddress);425      expect(event.returnValues.from).to.equal(caller);426      expect(event.returnValues.to).to.equal(receiver);427      expect(event.returnValues.value).to.equal('15');428    }429430    {431      const balance = await contract.methods.balanceOf(receiver).call();432      expect(+balance).to.equal(1);433    }434435    {436      const balance = await contract.methods.balanceOf(caller).call();437      expect(+balance).to.equal(0);438    }439  });440441  // Soft-deprecated442  itEth('Can perform burnFrom()', async ({helper}) => {443    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});444445    const owner = await helper.eth.createAccountWithBalance(donor);446    const spender = await helper.eth.createAccountWithBalance(donor);447448    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});449450    const address = helper.ethAddress.fromCollectionId(collection.collectionId);451    const contract = await helper.ethNativeContract.collection(address, 'rft', spender, true);452453    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);454    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner);455    await tokenContract.methods.repartition(15).send();456    await tokenContract.methods.approve(spender, 15).send();457458    {459      const result = await contract.methods.burnFrom(owner, token.tokenId).send();460      const event = result.events.Transfer;461      expect(event).to.be.like({462        address: helper.ethAddress.fromCollectionId(collection.collectionId),463        event: 'Transfer',464        returnValues: {465          from: owner,466          to: '0x0000000000000000000000000000000000000000',467          tokenId: token.tokenId.toString(),468        },469      });470    }471472    expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);473  });474475  itEth('Can perform burnFromCross()', async ({helper}) => {476    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});477478    const owner = bob;479    const spender = await helper.eth.createAccountWithBalance(donor);480481    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});482483    const address = helper.ethAddress.fromCollectionId(collection.collectionId);484    const contract = await helper.ethNativeContract.collection(address, 'rft');485486    await token.repartition(owner, 15n);487    await token.approve(owner, {Ethereum: spender}, 15n);488489    {490      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);491      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});492      const event = result.events.Transfer;493      expect(event).to.be.like({494        address: helper.ethAddress.fromCollectionId(collection.collectionId),495        event: 'Transfer',496        returnValues: {497          from: helper.address.substrateToEth(owner.address),498          to: '0x0000000000000000000000000000000000000000',499          tokenId: token.tokenId.toString(),500        },501      });502    }503504    expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);505  });506507  itEth('Can perform transferFromCross()', async ({helper}) => {508    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});509510    const owner = bob;511    const spender = await helper.eth.createAccountWithBalance(donor);512    const receiver = charlie;513514    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});515516    const address = helper.ethAddress.fromCollectionId(collection.collectionId);517    const contract = await helper.ethNativeContract.collection(address, 'rft');518519    await token.repartition(owner, 15n);520    await token.approve(owner, {Ethereum: spender}, 15n);521522    {523      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);524      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);525      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});526      const event = result.events.Transfer;527      expect(event).to.be.like({528        address: helper.ethAddress.fromCollectionId(collection.collectionId),529        event: 'Transfer',530        returnValues: {531          from: helper.address.substrateToEth(owner.address),532          to: helper.address.substrateToEth(receiver.address),533          tokenId: token.tokenId.toString(),534        },535      });536    }537538    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);539  });540541  itEth('Can perform transfer()', async ({helper}) => {542    const caller = await helper.eth.createAccountWithBalance(donor);543    const receiver = helper.eth.createAccount();544    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');545    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);546547    const result = await contract.methods.mint(caller).send();548    const tokenId = result.events.Transfer.returnValues.tokenId;549550    {551      const result = await contract.methods.transfer(receiver, tokenId).send();552553      const event = result.events.Transfer;554      expect(event.address).to.equal(collectionAddress);555      expect(event.returnValues.from).to.equal(caller);556      expect(event.returnValues.to).to.equal(receiver);557      expect(event.returnValues.tokenId).to.equal(tokenId.toString());558    }559560    {561      const balance = await contract.methods.balanceOf(caller).call();562      expect(+balance).to.equal(0);563    }564565    {566      const balance = await contract.methods.balanceOf(receiver).call();567      expect(+balance).to.equal(1);568    }569  });570571  itEth('Can perform transferCross()', async ({helper}) => {572    const sender = await helper.eth.createAccountWithBalance(donor);573    const receiverEth = await helper.eth.createAccountWithBalance(donor);574    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);575    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);576577    const collection = await helper.rft.mintCollection(minter, {});578    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);579    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);580581    const token = await collection.mintToken(minter, 50n, {Ethereum: sender});582583    {584      // Can transferCross to ethereum address:585      const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});586      // Check events:587      const event = result.events.Transfer;588      expect(event.address).to.equal(collectionAddress);589      expect(event.returnValues.from).to.equal(sender);590      expect(event.returnValues.to).to.equal(receiverEth);591      expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());592      // Sender's balance decreased:593      const senderBalance = await collectionEvm.methods.balanceOf(sender).call();594      expect(+senderBalance).to.equal(0);595      expect(await token.getBalance({Ethereum: sender})).to.eq(0n);596      // Receiver's balance increased:597      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();598      expect(+receiverBalance).to.equal(1);599      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);600    }601602    {603      // Can transferCross to substrate address:604      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});605      // Check events:606      const event = substrateResult.events.Transfer;607      expect(event.address).to.be.equal(collectionAddress);608      expect(event.returnValues.from).to.be.equal(receiverEth);609      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));610      expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);611      // Sender's balance decreased:612      const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();613      expect(+senderBalance).to.equal(0);614      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);615      // Receiver's balance increased:616      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});617      expect(receiverBalance).to.contain(token.tokenId);618      expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);619    }620  });621622  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {623    const sender = await helper.eth.createAccountWithBalance(donor);624    const tokenOwner = await helper.eth.createAccountWithBalance(donor);625    const receiverSub = minter;626    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);627628    const collection = await helper.rft.mintCollection(minter, {});629    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);630    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);631632    await collection.mintToken(minter, 50n, {Ethereum: sender});633    const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});634635    // Cannot transferCross someone else's token:636    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;637    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;638    // Cannot transfer token if it does not exist:639    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;640  }));641642  itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {643    const caller = await helper.eth.createAccountWithBalance(donor);644    const receiver = helper.eth.createAccount();645    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');646    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);647648    const result = await contract.methods.mint(caller).send();649    const tokenId = result.events.Transfer.returnValues.tokenId;650651    const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);652653    await tokenContract.methods.repartition(2).send();654    await tokenContract.methods.transfer(receiver, 1).send();655656    const events: any = [];657    contract.events.allEvents((_: any, event: any) => {658      events.push(event);659    });660661    await tokenContract.methods.transfer(receiver, 1).send();662    if(events.length == 0) await helper.wait.newBlocks(1);663    const event = events[0];664665    expect(event.address).to.equal(collectionAddress);666    expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');667    expect(event.returnValues.to).to.equal(receiver);668    expect(event.returnValues.tokenId).to.equal(tokenId.toString());669  });670671  itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {672    const caller = await helper.eth.createAccountWithBalance(donor);673    const receiver = helper.eth.createAccount();674    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');675    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);676677    const result = await contract.methods.mint(caller).send();678    const tokenId = result.events.Transfer.returnValues.tokenId;679680    const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);681682    await tokenContract.methods.repartition(2).send();683684    const events: any = [];685    contract.events.allEvents((_: any, event: any) => {686      events.push(event);687    });688689    await tokenContract.methods.transfer(receiver, 1).send();690    if(events.length == 0) await helper.wait.newBlocks(1);691    const event = events[0];692693    expect(event.address).to.equal(collectionAddress);694    expect(event.returnValues.from).to.equal(caller);695    expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');696    expect(event.returnValues.tokenId).to.equal(tokenId.toString());697  });698699  itEth('Check balanceOfCross()', async ({helper}) => {700    const collection = await helper.rft.mintCollection(minter, {});701    const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);702    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);703    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner.eth);704705    expect(await collectionEvm.methods.balanceOfCross(owner).call({from: owner.eth})).to.be.eq('0');706707    for(let i = 1n; i < 10n; i++) {708      await collection.mintToken(minter, 100n, {Ethereum: owner.eth});709      expect(await collectionEvm.methods.balanceOfCross(owner).call({from: owner.eth})).to.be.eq(i.toString());710    }711  });712713  itEth('Check ownerOfCross()', async ({helper}) => {714    const collection = await helper.rft.mintCollection(minter, {});715    let owner = await helper.ethCrossAccount.createAccountWithBalance(donor);716    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);717    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner.eth);718    const {tokenId} = await collection.mintToken(minter, 100n,{Ethereum: owner.eth});719720    for(let i = 1n; i < 10n; i++) {721      const ownerCross = await collectionEvm.methods.ownerOfCross(tokenId).call({from: owner.eth});722      expect(ownerCross.eth).to.be.eq(owner.eth);723      expect(ownerCross.sub).to.be.eq(owner.sub);724725      const newOwner = await helper.ethCrossAccount.createAccountWithBalance(donor);726      await collectionEvm.methods.transferCross(newOwner, tokenId).send({from: owner.eth});727      owner = newOwner;728    }729730    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId);731    const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner.eth, true);732    const newOwner = await helper.ethCrossAccount.createAccountWithBalance(donor);733    await tokenContract.methods.transferCross(newOwner, 50).send({from: owner.eth});734    const ownerCross = await collectionEvm.methods.ownerOfCross(tokenId).call({from: owner.eth});735    expect(ownerCross.eth.toUpperCase()).to.be.eq('0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF');736    expect(ownerCross.sub).to.be.eq('0');737  });738});739740describe('RFT: Fees', () => {741  let donor: IKeyringPair;742743  before(async function() {744    await usingEthPlaygrounds(async (helper, privateKey) => {745      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);746747      donor = await privateKey({url: import.meta.url});748    });749  });750751  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {752    const caller = await helper.eth.createAccountWithBalance(donor);753    const receiver = helper.eth.createAccount();754    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');755    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);756757    const result = await contract.methods.mint(caller).send();758    const tokenId = result.events.Transfer.returnValues.tokenId;759760    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());761    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));762    expect(cost > 0n);763  });764765  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {766    const caller = await helper.eth.createAccountWithBalance(donor);767    const receiver = helper.eth.createAccount();768    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');769    const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);770771    const result = await contract.methods.mint(caller).send();772    const tokenId = result.events.Transfer.returnValues.tokenId;773774    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());775    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));776    expect(cost > 0n);777  });778});779780describe('Common metadata', () => {781  let donor: IKeyringPair;782  let alice: IKeyringPair;783784  before(async function() {785    await usingEthPlaygrounds(async (helper, privateKey) => {786      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);787788      donor = await privateKey({url: import.meta.url});789      [alice] = await helper.arrange.createAccounts([1000n], donor);790    });791  });792793  itEth('Returns collection name', async ({helper}) => {794    const caller = helper.eth.createAccount();795    const tokenPropertyPermissions = [{796      key: 'URI',797      permission: {798        mutable: true,799        collectionAdmin: true,800        tokenOwner: false,801      },802    }];803    const collection = await helper.rft.mintCollection(804      alice,805      {806        name: 'Leviathan',807        tokenPrefix: '11',808        properties: [{key: 'ERC721Metadata', value: '1'}],809        tokenPropertyPermissions,810      },811    );812813    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);814    const name = await contract.methods.name().call();815    expect(name).to.equal('Leviathan');816  });817818  itEth('Returns symbol name', async ({helper}) => {819    const caller = await helper.eth.createAccountWithBalance(donor);820    const tokenPropertyPermissions = [{821      key: 'URI',822      permission: {823        mutable: true,824        collectionAdmin: true,825        tokenOwner: false,826      },827    }];828    const {collectionId} = await helper.rft.mintCollection(829      alice,830      {831        name: 'Leviathan',832        tokenPrefix: '12',833        properties: [{key: 'ERC721Metadata', value: '1'}],834        tokenPropertyPermissions,835      },836    );837838    const contract = await helper.ethNativeContract.collectionById(collectionId, 'rft', caller);839    const symbol = await contract.methods.symbol().call();840    expect(symbol).to.equal('12');841  });842});843844describe('Negative tests', () => {845  let donor: IKeyringPair;846  let minter: IKeyringPair;847  let alice: IKeyringPair;848849  before(async function() {850    await usingEthPlaygrounds(async (helper, privateKey) => {851      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);852853      donor = await privateKey({url: import.meta.url});854      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);855    });856  });857858  itEth('[negative] Cant perform burn without approval', async ({helper}) => {859    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});860861    const owner = await helper.eth.createAccountWithBalance(donor);862    const spender = await helper.eth.createAccountWithBalance(donor);863864    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});865866    const address = helper.ethAddress.fromCollectionId(collection.collectionId);867    const contract = await helper.ethNativeContract.collection(address, 'rft');868869    const ownerCross = helper.ethCrossAccount.fromAddress(owner);870871    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;872873    await contract.methods.setApprovalForAll(spender, true).send({from: owner});874    await contract.methods.setApprovalForAll(spender, false).send({from: owner});875876    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;877  });878879  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {880    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});881    const owner = await helper.eth.createAccountWithBalance(donor);882    const receiver = alice;883884    const spender = await helper.eth.createAccountWithBalance(donor);885886    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});887888    const address = helper.ethAddress.fromCollectionId(collection.collectionId);889    const contract = await helper.ethNativeContract.collection(address, 'rft');890891    const ownerCross = helper.ethCrossAccount.fromAddress(owner);892    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);893894    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;895896    await contract.methods.setApprovalForAll(spender, true).send({from: owner});897    await contract.methods.setApprovalForAll(spender, false).send({from: owner});898899    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;900  });901});
after · tests/src/eth/reFungible.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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';20import {ITokenPropertyPermission} from '../util/playgrounds/types';21import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types';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});