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

difftreelog

source

tests/src/eth/nonFungible.test.ts44.5 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 {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';20import {ITokenPropertyPermission} from '../util/playgrounds/types';2122describe('Check ERC721 token URI for NFT', () => {23  let donor: IKeyringPair;2425  before(async function() {26    await usingEthPlaygrounds(async (_helper, privateKey) => {27      donor = await privateKey({filename: __filename});28    });29  });3031  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {32    const owner = await helper.eth.createAccountWithBalance(donor);33    const receiver = helper.eth.createAccount();3435    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);36    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);3738    const result = await contract.methods.mint(receiver).send();39    const tokenId = result.events.Transfer.returnValues.tokenId;40    expect(tokenId).to.be.equal('1');4142    if (propertyKey && propertyValue) {43      // Set URL or suffix44      await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();45    }4647    const event = result.events.Transfer;48    expect(event.address).to.be.equal(collectionAddress);49    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');50    expect(event.returnValues.to).to.be.equal(receiver);51    expect(event.returnValues.tokenId).to.be.equal(tokenId);5253    return {contract, nextTokenId: tokenId};54  }5556  itEth('Empty tokenURI', async ({helper}) => {57    const {contract, nextTokenId} = await setup(helper, '');58    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');59  });6061  itEth('TokenURI from url', async ({helper}) => {62    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');63    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');64  });6566  itEth('TokenURI from baseURI', async ({helper}) => {67    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');68    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');69  });7071  itEth('TokenURI from baseURI + suffix', async ({helper}) => {72    const suffix = '/some/suffix';73    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);74    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);75  });76});7778describe('NFT: Plain calls', () => {79  let donor: IKeyringPair;80  let minter: IKeyringPair;81  let bob: IKeyringPair;82  let charlie: IKeyringPair;8384  before(async function() {85    await usingEthPlaygrounds(async (helper, privateKey) => {86      donor = await privateKey({filename: __filename});87      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);88    });89  });9091  itEth('Can perform mint() & get crossOwner()', async ({helper}) => {92    const owner = await helper.eth.createAccountWithBalance(donor);93    const receiver = helper.eth.createAccount();9495    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');96    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9798    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();99    const tokenId = result.events.Transfer.returnValues.tokenId;100    expect(tokenId).to.be.equal('1');101102    const event = result.events.Transfer;103    expect(event.address).to.be.equal(collectionAddress);104    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');105    expect(event.returnValues.to).to.be.equal(receiver);106107    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');108    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);109    // TODO: this wont work right now, need release 919000 first110    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();111    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();112    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);113  });114115  // TODO combine all minting tests in one place116  [117    'substrate' as const,118    'ethereum' as const,119  ].map(testCase => {120    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {121      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);122123      const receiverEth = helper.eth.createAccount();124      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);125      const receiverSub = bob;126      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);127128      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);129      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });130      const permissions: ITokenPropertyPermission[] = properties131        .map(p => {132          return {133            key: p.key, permission: {134              tokenOwner: false,135              collectionAdmin: true,136              mutable: false,137            },138          };139        });140141      const collection = await helper.nft.mintCollection(minter, {142        tokenPrefix: 'ethp',143        tokenPropertyPermissions: permissions,144      });145      await collection.addAdmin(minter, {Ethereum: collectionAdmin});146147      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);148      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);149      let expectedTokenId = await contract.methods.nextTokenId().call();150      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();151      let tokenId = result.events.Transfer.returnValues.tokenId;152      expect(tokenId).to.be.equal(expectedTokenId);153154      let event = result.events.Transfer;155      expect(event.address).to.be.equal(collectionAddress);156      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');157      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));158      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);159160      expectedTokenId = await contract.methods.nextTokenId().call();161      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();162      event = result.events.Transfer;163      expect(event.address).to.be.equal(collectionAddress);164      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');165      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));166      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);167168      tokenId = result.events.Transfer.returnValues.tokenId;169170      expect(tokenId).to.be.equal(expectedTokenId);171172      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties173        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));174175      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))176        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});177    });178  });179180  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {181    const nonOwner = await helper.eth.createAccountWithBalance(donor);182    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);183184    const collection = await helper.nft.mintCollection(minter);185    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);186    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');187188    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))189      .to.be.rejectedWith('PublicMintingNotAllowed');190  });191192  //TODO: CORE-302 add eth methods193  itEth.skip('Can perform mintBulk()', async ({helper}) => {194    const caller = await helper.eth.createAccountWithBalance(donor);195    const receiver = helper.eth.createAccount();196197    const collection = await helper.nft.mintCollection(minter);198    await collection.addAdmin(minter, {Ethereum: caller});199200    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);201    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);202    {203      const bulkSize = 3;204      const nextTokenId = await contract.methods.nextTokenId().call();205      expect(nextTokenId).to.be.equal('1');206      const result = await contract.methods.mintBulkWithTokenURI(207        receiver,208        Array.from({length: bulkSize}, (_, i) => (209          [+nextTokenId + i, `Test URI ${i}`]210        )),211      ).send({from: caller});212213      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);214      for (let i = 0; i < bulkSize; i++) {215        const event = events[i];216        expect(event.address).to.equal(collectionAddress);217        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');218        expect(event.returnValues.to).to.equal(receiver);219        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);220221        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);222      }223    }224  });225226  itEth('Can perform burn()', async ({helper}) => {227    const caller = await helper.eth.createAccountWithBalance(donor);228229    const collection = await helper.nft.mintCollection(minter, {});230    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});231232    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);233    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);234235    {236      const result = await contract.methods.burn(tokenId).send({from: caller});237238      const event = result.events.Transfer;239      expect(event.address).to.be.equal(collectionAddress);240      expect(event.returnValues.from).to.be.equal(caller);241      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');242      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);243    }244  });245246  itEth('Can perform approve()', async ({helper}) => {247    const owner = await helper.eth.createAccountWithBalance(donor);248    const spender = helper.eth.createAccount();249250    const collection = await helper.nft.mintCollection(minter, {});251    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});252253    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);254    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);255256    {257      const result = await contract.methods.approve(spender, tokenId).send({from: owner});258259      const event = result.events.Approval;260      expect(event.address).to.be.equal(collectionAddress);261      expect(event.returnValues.owner).to.be.equal(owner);262      expect(event.returnValues.approved).to.be.equal(spender);263      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);264    }265  });266267  itEth('Can perform setApprovalForAll()', async ({helper}) => {268    const owner = await helper.eth.createAccountWithBalance(donor);269    const operator = helper.eth.createAccount();270271    const collection = await helper.nft.mintCollection(minter, {});272273    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);274    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);275276    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();277    expect(approvedBefore).to.be.equal(false);278279    {280      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});281282      expect(result.events.ApprovalForAll).to.be.like({283        address: collectionAddress,284        event: 'ApprovalForAll',285        returnValues: {286          owner,287          operator,288          approved: true,289        },290      });291292      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();293      expect(approvedAfter).to.be.equal(true);294    }295296    {297      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});298299      expect(result.events.ApprovalForAll).to.be.like({300        address: collectionAddress,301        event: 'ApprovalForAll',302        returnValues: {303          owner,304          operator,305          approved: false,306        },307      });308309      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();310      expect(approvedAfter).to.be.equal(false);311    }312  });313314  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {315    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});316317    const owner = await helper.eth.createAccountWithBalance(donor);318    const operator = await helper.eth.createAccountWithBalance(donor, 100n);319320    const token = await collection.mintToken(minter, {Ethereum: owner});321322    const address = helper.ethAddress.fromCollectionId(collection.collectionId);323    const contract = await helper.ethNativeContract.collection(address, 'nft');324325    {326      await contract.methods.setApprovalForAll(operator, true).send({from: owner});327      const ownerCross = helper.ethCrossAccount.fromAddress(owner);328      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});329      const events = result.events.Transfer;330331      expect(events).to.be.like({332        address,333        event: 'Transfer',334        returnValues: {335          from: owner,336          to: '0x0000000000000000000000000000000000000000',337          tokenId: token.tokenId.toString(),338        },339      });340    }341342    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;343  });344345  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {346    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});347348    const owner = await helper.eth.createAccountWithBalance(donor);349    const operator = await helper.eth.createAccountWithBalance(donor);350    const receiver = charlie;351352    const token = await collection.mintToken(minter, {Ethereum: owner});353354    const address = helper.ethAddress.fromCollectionId(collection.collectionId);355    const contract = await helper.ethNativeContract.collection(address, 'nft');356357    {358      await contract.methods.setApprovalForAll(operator, true).send({from: owner});359      const ownerCross = helper.ethCrossAccount.fromAddress(owner);360      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);361      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});362      const event = result.events.Transfer;363      expect(event).to.be.like({364        address: helper.ethAddress.fromCollectionId(collection.collectionId),365        event: 'Transfer',366        returnValues: {367          from: owner,368          to: helper.address.substrateToEth(receiver.address),369          tokenId: token.tokenId.toString(),370        },371      });372    }373374    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});375  });376377  itEth('Can perform burnFromCross()', async ({helper}) => {378    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});379    const ownerSub = bob;380    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);381    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);382    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);383384    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);385    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);386387    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});388    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});389390    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);391    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');392393    // Approve tokens from substrate and ethereum:394    await token1.approve(ownerSub, {Ethereum: burnerEth});395    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});396397    // can burnFromCross:398    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});399    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});400    const events1 = result1.events.Transfer;401    const events2 = result2.events.Transfer;402403    // Check events for burnFromCross (substrate and ethereum):404    [405      [events1, token1, helper.address.substrateToEth(ownerSub.address)],406      [events2, token2, ownerEth],407    ].map(burnData => {408      expect(burnData[0]).to.be.like({409        address: collectionAddress,410        event: 'Transfer',411        returnValues: {412          from: burnData[2],413          to: '0x0000000000000000000000000000000000000000',414          tokenId: burnData[1].tokenId.toString(),415        },416      });417    });418419    expect(await token1.doesExist()).to.be.false;420    expect(await token2.doesExist()).to.be.false;421  });422423  // TODO combine all approve tests in one place424  itEth('Can perform approveCross()', async ({helper}) => {425    // arrange: create accounts426    const owner = await helper.eth.createAccountWithBalance(donor, 100n);427    const ownerCross = helper.ethCrossAccount.fromAddress(owner);428    const receiverSub = charlie;429    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);430    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);431    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);432433    // arrange: create collection and tokens:434    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});435    const token1 = await collection.mintToken(minter, {Ethereum: owner});436    const token2 = await collection.mintToken(minter, {Ethereum: owner});437438    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');439440    // Can approveCross substrate and ethereum address:441    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});442    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});443    const eventSub = resultSub.events.Approval;444    const eventEth = resultEth.events.Approval;445    expect(eventSub).to.be.like({446      address: helper.ethAddress.fromCollectionId(collection.collectionId),447      event: 'Approval',448      returnValues: {449        owner,450        approved: helper.address.substrateToEth(receiverSub.address),451        tokenId: token1.tokenId.toString(),452      },453    });454    expect(eventEth).to.be.like({455      address: helper.ethAddress.fromCollectionId(collection.collectionId),456      event: 'Approval',457      returnValues: {458        owner,459        approved: receiverEth,460        tokenId: token2.tokenId.toString(),461      },462    });463464    // Substrate address can transferFrom approved tokens:465    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});466    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});467    // Ethereum address can transferFromCross approved tokens:468    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});469    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});470  });471472  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {473    const nonOwner = await helper.eth.createAccountWithBalance(donor);474    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);475    const owner = await helper.eth.createAccountWithBalance(donor);476    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});477    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');478    const token = await collection.mintToken(minter, {Ethereum: owner});479480    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');481  });482483  itEth('Can reaffirm approved address', async ({helper}) => {484    const owner = await helper.eth.createAccountWithBalance(donor, 100n);485    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);486    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);487    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);488    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);489    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});490    const token1 = await collection.mintToken(minter, {Ethereum: owner});491    const token2 = await collection.mintToken(minter, {Ethereum: owner});492    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');493494    // Can approve and reaffirm approved address:495    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});496    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});497498    // receiver1 cannot transferFrom:499    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;500    // receiver2 can transferFrom:501    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});502503    // can set approved address to self address to remove approval:504    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});505    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});506507    // receiver1 cannot transfer token anymore:508    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;509  });510511  itEth('Can perform transferFrom()', async ({helper}) => {512    const owner = await helper.eth.createAccountWithBalance(donor);513    const spender = await helper.eth.createAccountWithBalance(donor);514    const receiver = helper.eth.createAccount();515516    const collection = await helper.nft.mintCollection(minter, {});517    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});518519    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);520    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);521522    await contract.methods.approve(spender, tokenId).send({from: owner});523524    {525      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});526527      const event = result.events.Transfer;528      expect(event.address).to.be.equal(collectionAddress);529      expect(event.returnValues.from).to.be.equal(owner);530      expect(event.returnValues.to).to.be.equal(receiver);531      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);532    }533534    {535      const balance = await contract.methods.balanceOf(receiver).call();536      expect(+balance).to.equal(1);537    }538539    {540      const balance = await contract.methods.balanceOf(owner).call();541      expect(+balance).to.equal(0);542    }543  });544545  itEth('Can perform transferFromCross()', async ({helper}) => {546    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});547548    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);549    const spender = await helper.eth.createAccountWithBalance(donor);550551    const token = await collection.mintToken(minter, {Substrate: owner.address});552553    const address = helper.ethAddress.fromCollectionId(collection.collectionId);554    const contract = await helper.ethNativeContract.collection(address, 'nft');555556    await token.approve(owner, {Ethereum: spender});557558    {559      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);560      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);561      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});562      const event = result.events.Transfer;563      expect(event).to.be.like({564        address: helper.ethAddress.fromCollectionId(collection.collectionId),565        event: 'Transfer',566        returnValues: {567          from: helper.address.substrateToEth(owner.address),568          to: helper.address.substrateToEth(receiver.address),569          tokenId: token.tokenId.toString(),570        },571      });572    }573574    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});575  });576577  itEth('Can perform transfer()', async ({helper}) => {578    const collection = await helper.nft.mintCollection(minter, {});579    const owner = await helper.eth.createAccountWithBalance(donor);580    const receiver = helper.eth.createAccount();581582    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});583584    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);585    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);586587    {588      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});589590      const event = result.events.Transfer;591      expect(event.address).to.be.equal(collectionAddress);592      expect(event.returnValues.from).to.be.equal(owner);593      expect(event.returnValues.to).to.be.equal(receiver);594      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);595    }596597    {598      const balance = await contract.methods.balanceOf(owner).call();599      expect(+balance).to.equal(0);600    }601602    {603      const balance = await contract.methods.balanceOf(receiver).call();604      expect(+balance).to.equal(1);605    }606  });607608  itEth('Can perform transferCross()', async ({helper}) => {609    const collection = await helper.nft.mintCollection(minter, {});610    const owner = await helper.eth.createAccountWithBalance(donor);611    const receiverEth = await helper.eth.createAccountWithBalance(donor);612    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);613    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);614615    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});616617    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);618    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);619620    {621      // Can transferCross to ethereum address:622      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});623      // Check events:624      const event = result.events.Transfer;625      expect(event.address).to.be.equal(collectionAddress);626      expect(event.returnValues.from).to.be.equal(owner);627      expect(event.returnValues.to).to.be.equal(receiverEth);628      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);629630      // owner has balance = 0:631      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();632      expect(+ownerBalance).to.equal(0);633      // receiver owns token:634      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();635      expect(+receiverBalance).to.equal(1);636      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});637    }638639    {640      // Can transferCross to substrate address:641      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});642      // Check events:643      const event = substrateResult.events.Transfer;644      expect(event.address).to.be.equal(collectionAddress);645      expect(event.returnValues.from).to.be.equal(receiverEth);646      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));647      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);648649      // owner has balance = 0:650      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();651      expect(+ownerBalance).to.equal(0);652      // receiver owns token:653      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});654      expect(receiverBalance).to.contain(tokenId);655    }656  });657658  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {659    const sender = await helper.eth.createAccountWithBalance(donor);660    const tokenOwner = await helper.eth.createAccountWithBalance(donor);661    const receiverSub = minter;662    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);663664    const collection = await helper.nft.mintCollection(minter, {});665    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);666    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);667668    await collection.mintToken(minter, {Ethereum: sender});669    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});670671    // Cannot transferCross someone else's token:672    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;673    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;674    // Cannot transfer token if it does not exist:675    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;676  }));677});678679describe('NFT: Fees', () => {680  let donor: IKeyringPair;681  let alice: IKeyringPair;682  let bob: IKeyringPair;683  let charlie: IKeyringPair;684685  before(async function() {686    await usingEthPlaygrounds(async (helper, privateKey) => {687      donor = await privateKey({filename: __filename});688      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);689    });690  });691692  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {693    const owner = await helper.eth.createAccountWithBalance(donor);694    const spender = helper.eth.createAccount();695696    const collection = await helper.nft.mintCollection(alice, {});697    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});698699    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);700701    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));702    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));703  });704705  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {706    const owner = await helper.eth.createAccountWithBalance(donor);707    const spender = await helper.eth.createAccountWithBalance(donor);708709    const collection = await helper.nft.mintCollection(alice, {});710    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});711712    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);713714    await contract.methods.approve(spender, tokenId).send({from: owner});715716    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));717    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));718  });719720  itEth('Can perform transferFromCross()', async ({helper}) => {721    const collectionMinter = alice;722    const owner = bob;723    const receiver = charlie;724    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});725726    const spender = await helper.eth.createAccountWithBalance(donor, 100n);727728    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});729730    const address = helper.ethAddress.fromCollectionId(collection.collectionId);731    const contract = await helper.ethNativeContract.collection(address, 'nft');732733    await token.approve(owner, {Ethereum: spender});734735    {736      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);737      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);738      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});739      const event = result.events.Transfer;740      expect(event).to.be.like({741        address: helper.ethAddress.fromCollectionId(collection.collectionId),742        event: 'Transfer',743        returnValues: {744          from: helper.address.substrateToEth(owner.address),745          to: helper.address.substrateToEth(receiver.address),746          tokenId: token.tokenId.toString(),747        },748      });749    }750751    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});752  });753754  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {755    const owner = await helper.eth.createAccountWithBalance(donor);756    const receiver = helper.eth.createAccount();757758    const collection = await helper.nft.mintCollection(alice, {});759    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});760761    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);762763    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));764    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));765  });766});767768describe('NFT: Substrate calls', () => {769  let donor: IKeyringPair;770  let alice: IKeyringPair;771772  before(async function() {773    await usingEthPlaygrounds(async (helper, privateKey) => {774      donor = await privateKey({filename: __filename});775      [alice] = await helper.arrange.createAccounts([20n], donor);776    });777  });778779  itEth('Events emitted for mint()', async ({helper}) => {780    const collection = await helper.nft.mintCollection(alice, {});781    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);782    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');783784    const events: any = [];785    contract.events.allEvents((_: any, event: any) => {786      events.push(event);787    });788789    const {tokenId} = await collection.mintToken(alice);790    if (events.length == 0) await helper.wait.newBlocks(1);791    const event = events[0];792793    expect(event.event).to.be.equal('Transfer');794    expect(event.address).to.be.equal(collectionAddress);795    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');796    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));797    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());798  });799800  itEth('Events emitted for burn()', async ({helper}) => {801    const collection = await helper.nft.mintCollection(alice, {});802    const token = await collection.mintToken(alice);803804    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);805    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');806807    const events: any = [];808    contract.events.allEvents((_: any, event: any) => {809      events.push(event);810    });811812    await token.burn(alice);813    if (events.length == 0) await helper.wait.newBlocks(1);814    const event = events[0];815816    expect(event.event).to.be.equal('Transfer');817    expect(event.address).to.be.equal(collectionAddress);818    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));819    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');820    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());821  });822823  itEth('Events emitted for approve()', async ({helper}) => {824    const receiver = helper.eth.createAccount();825826    const collection = await helper.nft.mintCollection(alice, {});827    const token = await collection.mintToken(alice);828829    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);830    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');831832    const events: any = [];833    contract.events.allEvents((_: any, event: any) => {834      events.push(event);835    });836837    await token.approve(alice, {Ethereum: receiver});838    if (events.length == 0) await helper.wait.newBlocks(1);839    const event = events[0];840841    expect(event.event).to.be.equal('Approval');842    expect(event.address).to.be.equal(collectionAddress);843    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));844    expect(event.returnValues.approved).to.be.equal(receiver);845    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());846  });847848  itEth('Events emitted for transferFrom()', async ({helper}) => {849    const [bob] = await helper.arrange.createAccounts([10n], donor);850    const receiver = helper.eth.createAccount();851852    const collection = await helper.nft.mintCollection(alice, {});853    const token = await collection.mintToken(alice);854    await token.approve(alice, {Substrate: bob.address});855856    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);857    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');858859    const events: any = [];860    contract.events.allEvents((_: any, event: any) => {861      events.push(event);862    });863864    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});865866    if (events.length == 0) await helper.wait.newBlocks(1);867    const event = events[0];868869    expect(event.address).to.be.equal(collectionAddress);870    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));871    expect(event.returnValues.to).to.be.equal(receiver);872    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);873  });874875  itEth('Events emitted for transfer()', async ({helper}) => {876    const receiver = helper.eth.createAccount();877878    const collection = await helper.nft.mintCollection(alice, {});879    const token = await collection.mintToken(alice);880881    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);882    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');883884    const events: any = [];885    contract.events.allEvents((_: any, event: any) => {886      events.push(event);887    });888889    await token.transfer(alice, {Ethereum: receiver});890891    if (events.length == 0) await helper.wait.newBlocks(1);892    const event = events[0];893894    expect(event.address).to.be.equal(collectionAddress);895    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));896    expect(event.returnValues.to).to.be.equal(receiver);897    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);898  });899});900901describe('Common metadata', () => {902  let donor: IKeyringPair;903  let alice: IKeyringPair;904905  before(async function() {906    await usingEthPlaygrounds(async (helper, privateKey) => {907      donor = await privateKey({filename: __filename});908      [alice] = await helper.arrange.createAccounts([20n], donor);909    });910  });911912  itEth('Returns collection name', async ({helper}) => {913    const caller = await helper.eth.createAccountWithBalance(donor);914    const tokenPropertyPermissions = [{915      key: 'URI',916      permission: {917        mutable: true,918        collectionAdmin: true,919        tokenOwner: false,920      },921    }];922    const collection = await helper.nft.mintCollection(923      alice,924      {925        name: 'oh River',926        tokenPrefix: 'CHANGE',927        properties: [{key: 'ERC721Metadata', value: '1'}],928        tokenPropertyPermissions,929      },930    );931932    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);933    const name = await contract.methods.name().call();934    expect(name).to.equal('oh River');935  });936937  itEth('Returns symbol name', async ({helper}) => {938    const caller = await helper.eth.createAccountWithBalance(donor);939    const tokenPropertyPermissions = [{940      key: 'URI',941      permission: {942        mutable: true,943        collectionAdmin: true,944        tokenOwner: false,945      },946    }];947    const collection = await helper.nft.mintCollection(948      alice,949      {950        name: 'oh River',951        tokenPrefix: 'CHANGE',952        properties: [{key: 'ERC721Metadata', value: '1'}],953        tokenPropertyPermissions,954      },955    );956957    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);958    const symbol = await contract.methods.symbol().call();959    expect(symbol).to.equal('CHANGE');960  });961});962963describe('Negative tests', () => {964  let donor: IKeyringPair;965  let minter: IKeyringPair;966  let alice: IKeyringPair;967968  before(async function() {969    await usingEthPlaygrounds(async (helper, privateKey) => {970      donor = await privateKey({filename: __filename});971      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);972    });973  });974975  itEth('[negative] Cant perform burn without approval', async ({helper}) => {976    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});977978    const owner = await helper.eth.createAccountWithBalance(donor, 100n);979    const spender = await helper.eth.createAccountWithBalance(donor, 100n);980981    const token = await collection.mintToken(minter, {Ethereum: owner});982983    const address = helper.ethAddress.fromCollectionId(collection.collectionId);984    const contract = await helper.ethNativeContract.collection(address, 'nft');985986    const ownerCross = helper.ethCrossAccount.fromAddress(owner);987    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;988989    await contract.methods.setApprovalForAll(spender, true).send({from: owner});990    await contract.methods.setApprovalForAll(spender, false).send({from: owner});991992    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;993  });994995  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {996    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});997    const receiver = alice;998999    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1000    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10011002    const token = await collection.mintToken(minter, {Ethereum: owner});10031004    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1005    const contract = await helper.ethNativeContract.collection(address, 'nft');10061007    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1008    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);10091010    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;10111012    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1013    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10141015    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1016  });1017});