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

difftreelog

source

tests/src/eth/nonFungible.test.ts46.8 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';212223describe('NFT: Information getting', () => {24  let donor: IKeyringPair;25  let alice: IKeyringPair;2627  before(async function() {28    await usingEthPlaygrounds(async (helper, privateKey) => {29      donor = await privateKey({filename: __filename});30      [alice] = await helper.arrange.createAccounts([10n], donor);31    });32  });3334  itEth('totalSupply', async ({helper}) => {35    const collection = await helper.nft.mintCollection(alice, {});36    await collection.mintToken(alice);3738    const caller = await helper.eth.createAccountWithBalance(donor);3940    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);41    const totalSupply = await contract.methods.totalSupply().call();4243    expect(totalSupply).to.equal('1');44  });4546  itEth('balanceOf', async ({helper}) => {47    const collection = await helper.nft.mintCollection(alice, {});48    const caller = await helper.eth.createAccountWithBalance(donor);4950    await collection.mintToken(alice, {Ethereum: caller});51    await collection.mintToken(alice, {Ethereum: caller});52    await collection.mintToken(alice, {Ethereum: caller});5354    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);55    const balance = await contract.methods.balanceOf(caller).call();5657    expect(balance).to.equal('3');58  });5960  itEth('ownerOf', async ({helper}) => {61    const collection = await helper.nft.mintCollection(alice, {});62    const caller = await helper.eth.createAccountWithBalance(donor);6364    const token = await collection.mintToken(alice, {Ethereum: caller});6566    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6768    const owner = await contract.methods.ownerOf(token.tokenId).call();6970    expect(owner).to.equal(caller);71  });7273  itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {74    const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});75    const caller = helper.eth.createAccount();7677    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7879    expect(await contract.methods.name().call()).to.equal('test');80    expect(await contract.methods.symbol().call()).to.equal('TEST');81  });82});8384describe('Check ERC721 token URI for NFT', () => {85  let donor: IKeyringPair;8687  before(async function() {88    await usingEthPlaygrounds(async (_helper, privateKey) => {89      donor = await privateKey({filename: __filename});90    });91  });9293  async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {94    const owner = await helper.eth.createAccountWithBalance(donor);95    const receiver = helper.eth.createAccount();9697    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);98    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);99100    const result = await contract.methods.mint(receiver).send();101    const tokenId = result.events.Transfer.returnValues.tokenId;102    expect(tokenId).to.be.equal('1');103104    if (propertyKey && propertyValue) {105      // Set URL or suffix106      await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();107    }108109    const event = result.events.Transfer;110    expect(event.address).to.be.equal(collectionAddress);111    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');112    expect(event.returnValues.to).to.be.equal(receiver);113    expect(event.returnValues.tokenId).to.be.equal(tokenId);114115    return {contract, nextTokenId: tokenId};116  }117118  itEth('Empty tokenURI', async ({helper}) => {119    const {contract, nextTokenId} = await setup(helper, '');120    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');121  });122123  itEth('TokenURI from url', async ({helper}) => {124    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');125    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');126  });127128  itEth('TokenURI from baseURI', async ({helper}) => {129    const {contract, nextTokenId} = await setup(helper, 'BaseURI_');130    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');131  });132133  itEth('TokenURI from baseURI + suffix', async ({helper}) => {134    const suffix = '/some/suffix';135    const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);136    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);137  });138});139140describe('NFT: Plain calls', () => {141  let donor: IKeyringPair;142  let minter: IKeyringPair;143  let bob: IKeyringPair;144  let charlie: IKeyringPair;145146  before(async function() {147    await usingEthPlaygrounds(async (helper, privateKey) => {148      donor = await privateKey({filename: __filename});149      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);150    });151  });152153  itEth('Can perform mint() & get crossOwner()', async ({helper}) => {154    const owner = await helper.eth.createAccountWithBalance(donor);155    const receiver = helper.eth.createAccount();156157    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');158    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);159160    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();161    const tokenId = result.events.Transfer.returnValues.tokenId;162    expect(tokenId).to.be.equal('1');163164    const event = result.events.Transfer;165    expect(event.address).to.be.equal(collectionAddress);166    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');167    expect(event.returnValues.to).to.be.equal(receiver);168169    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');170    console.log(await contract.methods.crossOwnerOf(tokenId).call());171    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);172    // TODO: this wont work right now, need release 919000 first173    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();174    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();175    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);176  });177178  // TODO combine all minting tests in one place179  [180    'substrate' as const,181    'ethereum' as const,182  ].map(testCase => {183    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {184      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);185186      const receiverEth = helper.eth.createAccount();187      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);188      const receiverSub = bob;189      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);190191      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);192      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });193      const permissions: ITokenPropertyPermission[] = properties194        .map(p => {195          return {196            key: p.key, permission: {197              tokenOwner: false,198              collectionAdmin: true,199              mutable: false,200            },201          };202        });203204205      const collection = await helper.nft.mintCollection(minter, {206        tokenPrefix: 'ethp',207        tokenPropertyPermissions: permissions,208      });209      await collection.addAdmin(minter, {Ethereum: collectionAdmin});210211      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);212      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);213      let expectedTokenId = await contract.methods.nextTokenId().call();214      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();215      let tokenId = result.events.Transfer.returnValues.tokenId;216      expect(tokenId).to.be.equal(expectedTokenId);217218      let event = result.events.Transfer;219      expect(event.address).to.be.equal(collectionAddress);220      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');221      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));222      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);223224      expectedTokenId = await contract.methods.nextTokenId().call();225      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();226      event = result.events.Transfer;227      expect(event.address).to.be.equal(collectionAddress);228      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');229      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));230      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);231232      tokenId = result.events.Transfer.returnValues.tokenId;233234      expect(tokenId).to.be.equal(expectedTokenId);235236      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties237        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));238239      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))240        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});241    });242  });243244  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {245    const nonOwner = await helper.eth.createAccountWithBalance(donor);246    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);247248    const collection = await helper.nft.mintCollection(minter);249    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);250    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');251252    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))253      .to.be.rejectedWith('PublicMintingNotAllowed');254  });255256  //TODO: CORE-302 add eth methods257  itEth.skip('Can perform mintBulk()', async ({helper}) => {258    const caller = await helper.eth.createAccountWithBalance(donor);259    const receiver = helper.eth.createAccount();260261    const collection = await helper.nft.mintCollection(minter);262    await collection.addAdmin(minter, {Ethereum: caller});263264    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);265    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);266    {267      const bulkSize = 3;268      const nextTokenId = await contract.methods.nextTokenId().call();269      expect(nextTokenId).to.be.equal('1');270      const result = await contract.methods.mintBulkWithTokenURI(271        receiver,272        Array.from({length: bulkSize}, (_, i) => (273          [+nextTokenId + i, `Test URI ${i}`]274        )),275      ).send({from: caller});276277      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);278      for (let i = 0; i < bulkSize; i++) {279        const event = events[i];280        expect(event.address).to.equal(collectionAddress);281        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');282        expect(event.returnValues.to).to.equal(receiver);283        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);284285        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);286      }287    }288  });289290  itEth('Can perform burn()', async ({helper}) => {291    const caller = await helper.eth.createAccountWithBalance(donor);292293    const collection = await helper.nft.mintCollection(minter, {});294    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});295296    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);297    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);298299    {300      const result = await contract.methods.burn(tokenId).send({from: caller});301302      const event = result.events.Transfer;303      expect(event.address).to.be.equal(collectionAddress);304      expect(event.returnValues.from).to.be.equal(caller);305      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');306      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);307    }308  });309310  itEth('Can perform approve()', async ({helper}) => {311    const owner = await helper.eth.createAccountWithBalance(donor);312    const spender = helper.eth.createAccount();313314    const collection = await helper.nft.mintCollection(minter, {});315    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});316317    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);318    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);319320    {321      const result = await contract.methods.approve(spender, tokenId).send({from: owner});322323      const event = result.events.Approval;324      expect(event.address).to.be.equal(collectionAddress);325      expect(event.returnValues.owner).to.be.equal(owner);326      expect(event.returnValues.approved).to.be.equal(spender);327      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);328    }329  });330331  itEth('Can perform setApprovalForAll()', async ({helper}) => {332    const owner = await helper.eth.createAccountWithBalance(donor);333    const operator = helper.eth.createAccount();334335    const collection = await helper.nft.mintCollection(minter, {});336337    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);338    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);339340    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();341    expect(approvedBefore).to.be.equal(false);342343    {344      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});345346      expect(result.events.ApprovalForAll).to.be.like({347        address: collectionAddress,348        event: 'ApprovalForAll',349        returnValues: {350          owner,351          operator,352          approved: true,353        },354      });355356      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();357      expect(approvedAfter).to.be.equal(true);358    }359360    {361      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});362363      expect(result.events.ApprovalForAll).to.be.like({364        address: collectionAddress,365        event: 'ApprovalForAll',366        returnValues: {367          owner,368          operator,369          approved: false,370        },371      });372373      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();374      expect(approvedAfter).to.be.equal(false);375    }376  });377378  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {379    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});380381    const owner = await helper.eth.createAccountWithBalance(donor);382    const operator = await helper.eth.createAccountWithBalance(donor, 100n);383384    const token = await collection.mintToken(minter, {Ethereum: owner});385386    const address = helper.ethAddress.fromCollectionId(collection.collectionId);387    const contract = await helper.ethNativeContract.collection(address, 'nft');388389    {390      await contract.methods.setApprovalForAll(operator, true).send({from: owner});391      const ownerCross = helper.ethCrossAccount.fromAddress(owner);392      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});393      const events = result.events.Transfer;394395      expect(events).to.be.like({396        address,397        event: 'Transfer',398        returnValues: {399          from: owner,400          to: '0x0000000000000000000000000000000000000000',401          tokenId: token.tokenId.toString(),402        },403      });404    }405406    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;407  });408409  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {410    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});411412    const owner = await helper.eth.createAccountWithBalance(donor);413    const operator = await helper.eth.createAccountWithBalance(donor);414    const receiver = charlie;415416    const token = await collection.mintToken(minter, {Ethereum: owner});417418    const address = helper.ethAddress.fromCollectionId(collection.collectionId);419    const contract = await helper.ethNativeContract.collection(address, 'nft');420421    {422      await contract.methods.setApprovalForAll(operator, true).send({from: owner});423      const ownerCross = helper.ethCrossAccount.fromAddress(owner);424      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);425      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});426      const event = result.events.Transfer;427      expect(event).to.be.like({428        address: helper.ethAddress.fromCollectionId(collection.collectionId),429        event: 'Transfer',430        returnValues: {431          from: owner,432          to: helper.address.substrateToEth(receiver.address),433          tokenId: token.tokenId.toString(),434        },435      });436    }437438    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});439  });440441  itEth('Can perform burnFromCross()', async ({helper}) => {442    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});443    const ownerSub = bob;444    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);445    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);446    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);447448    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);449    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);450451    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});452    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});453454    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);455    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');456457    // Approve tokens from substrate and ethereum:458    await token1.approve(ownerSub, {Ethereum: burnerEth});459    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});460461    // can burnFromCross:462    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});463    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});464    const events1 = result1.events.Transfer;465    const events2 = result2.events.Transfer;466467    // Check events for burnFromCross (substrate and ethereum):468    [469      [events1, token1, helper.address.substrateToEth(ownerSub.address)],470      [events2, token2, ownerEth],471    ].map(burnData => {472      expect(burnData[0]).to.be.like({473        address: collectionAddress,474        event: 'Transfer',475        returnValues: {476          from: burnData[2],477          to: '0x0000000000000000000000000000000000000000',478          tokenId: burnData[1].tokenId.toString(),479        },480      });481    });482483    expect(await token1.doesExist()).to.be.false;484    expect(await token2.doesExist()).to.be.false;485  });486487  // TODO combine all approve tests in one place488  itEth('Can perform approveCross()', async ({helper}) => {489    // arrange: create accounts490    const owner = await helper.eth.createAccountWithBalance(donor, 100n);491    const ownerCross = helper.ethCrossAccount.fromAddress(owner);492    const receiverSub = charlie;493    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);494    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);495    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);496497    // arrange: create collection and tokens:498    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});499    const token1 = await collection.mintToken(minter, {Ethereum: owner});500    const token2 = await collection.mintToken(minter, {Ethereum: owner});501502    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');503504    // Can approveCross substrate and ethereum address:505    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});506    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});507    const eventSub = resultSub.events.Approval;508    const eventEth = resultEth.events.Approval;509    expect(eventSub).to.be.like({510      address: helper.ethAddress.fromCollectionId(collection.collectionId),511      event: 'Approval',512      returnValues: {513        owner,514        approved: helper.address.substrateToEth(receiverSub.address),515        tokenId: token1.tokenId.toString(),516      },517    });518    expect(eventEth).to.be.like({519      address: helper.ethAddress.fromCollectionId(collection.collectionId),520      event: 'Approval',521      returnValues: {522        owner,523        approved: receiverEth,524        tokenId: token2.tokenId.toString(),525      },526    });527528    // Substrate address can transferFrom approved tokens:529    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});530    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});531    // Ethereum address can transferFromCross approved tokens:532    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});533    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});534  });535536  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {537    const nonOwner = await helper.eth.createAccountWithBalance(donor);538    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);539    const owner = await helper.eth.createAccountWithBalance(donor);540    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});541    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');542    const token = await collection.mintToken(minter, {Ethereum: owner});543544    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');545  });546547  itEth('Can reaffirm approved address', async ({helper}) => {548    const owner = await helper.eth.createAccountWithBalance(donor, 100n);549    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);550    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);551    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);552    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);553    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});554    const token1 = await collection.mintToken(minter, {Ethereum: owner});555    const token2 = await collection.mintToken(minter, {Ethereum: owner});556    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');557558    // Can approve and reaffirm approved address:559    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});560    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});561562    // receiver1 cannot transferFrom:563    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;564    // receiver2 can transferFrom:565    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});566567    // can set approved address to self address to remove approval:568    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});569    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});570571    // receiver1 cannot transfer token anymore:572    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;573  });574575  itEth('Can perform transferFrom()', async ({helper}) => {576    const owner = await helper.eth.createAccountWithBalance(donor);577    const spender = await helper.eth.createAccountWithBalance(donor);578    const receiver = helper.eth.createAccount();579580    const collection = await helper.nft.mintCollection(minter, {});581    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});582583    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);584    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);585586    await contract.methods.approve(spender, tokenId).send({from: owner});587588    {589      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});590591      const event = result.events.Transfer;592      expect(event.address).to.be.equal(collectionAddress);593      expect(event.returnValues.from).to.be.equal(owner);594      expect(event.returnValues.to).to.be.equal(receiver);595      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);596    }597598    {599      const balance = await contract.methods.balanceOf(receiver).call();600      expect(+balance).to.equal(1);601    }602603    {604      const balance = await contract.methods.balanceOf(owner).call();605      expect(+balance).to.equal(0);606    }607  });608609  itEth('Can perform transferFromCross()', async ({helper}) => {610    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});611612    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);613    const spender = await helper.eth.createAccountWithBalance(donor);614615    const token = await collection.mintToken(minter, {Substrate: owner.address});616617    const address = helper.ethAddress.fromCollectionId(collection.collectionId);618    const contract = await helper.ethNativeContract.collection(address, 'nft');619620    await token.approve(owner, {Ethereum: spender});621622    {623      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);624      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);625      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});626      const event = result.events.Transfer;627      expect(event).to.be.like({628        address: helper.ethAddress.fromCollectionId(collection.collectionId),629        event: 'Transfer',630        returnValues: {631          from: helper.address.substrateToEth(owner.address),632          to: helper.address.substrateToEth(receiver.address),633          tokenId: token.tokenId.toString(),634        },635      });636    }637638    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});639  });640641  itEth('Can perform transfer()', async ({helper}) => {642    const collection = await helper.nft.mintCollection(minter, {});643    const owner = await helper.eth.createAccountWithBalance(donor);644    const receiver = helper.eth.createAccount();645646    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});647648    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);649    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);650651    {652      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});653654      const event = result.events.Transfer;655      expect(event.address).to.be.equal(collectionAddress);656      expect(event.returnValues.from).to.be.equal(owner);657      expect(event.returnValues.to).to.be.equal(receiver);658      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);659    }660661    {662      const balance = await contract.methods.balanceOf(owner).call();663      expect(+balance).to.equal(0);664    }665666    {667      const balance = await contract.methods.balanceOf(receiver).call();668      expect(+balance).to.equal(1);669    }670  });671672  itEth('Can perform transferCross()', async ({helper}) => {673    const collection = await helper.nft.mintCollection(minter, {});674    const owner = await helper.eth.createAccountWithBalance(donor);675    const receiverEth = await helper.eth.createAccountWithBalance(donor);676    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);677    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);678679    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});680681    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);682    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);683684    {685      // Can transferCross to ethereum address:686      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});687      // Check events:688      const event = result.events.Transfer;689      expect(event.address).to.be.equal(collectionAddress);690      expect(event.returnValues.from).to.be.equal(owner);691      expect(event.returnValues.to).to.be.equal(receiverEth);692      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);693694      // owner has balance = 0:695      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();696      expect(+ownerBalance).to.equal(0);697      // receiver owns token:698      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();699      expect(+receiverBalance).to.equal(1);700      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});701    }702703    {704      // Can transferCross to substrate address:705      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});706      // Check events:707      const event = substrateResult.events.Transfer;708      expect(event.address).to.be.equal(collectionAddress);709      expect(event.returnValues.from).to.be.equal(receiverEth);710      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));711      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);712713      // owner has balance = 0:714      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();715      expect(+ownerBalance).to.equal(0);716      // receiver owns token:717      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});718      expect(receiverBalance).to.contain(tokenId);719    }720  });721722  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {723    const sender = await helper.eth.createAccountWithBalance(donor);724    const tokenOwner = await helper.eth.createAccountWithBalance(donor);725    const receiverSub = minter;726    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);727728    const collection = await helper.nft.mintCollection(minter, {});729    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);730    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);731732    await collection.mintToken(minter, {Ethereum: sender});733    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});734735    // Cannot transferCross someone else's token:736    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;737    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;738    // Cannot transfer token if it does not exist:739    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;740  }));741});742743describe('NFT: Fees', () => {744  let donor: IKeyringPair;745  let alice: IKeyringPair;746  let bob: IKeyringPair;747  let charlie: IKeyringPair;748749  before(async function() {750    await usingEthPlaygrounds(async (helper, privateKey) => {751      donor = await privateKey({filename: __filename});752      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);753    });754  });755756  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {757    const owner = await helper.eth.createAccountWithBalance(donor);758    const spender = helper.eth.createAccount();759760    const collection = await helper.nft.mintCollection(alice, {});761    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});762763    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);764765    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));766    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));767  });768769  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {770    const owner = await helper.eth.createAccountWithBalance(donor);771    const spender = await helper.eth.createAccountWithBalance(donor);772773    const collection = await helper.nft.mintCollection(alice, {});774    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});775776    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);777778    await contract.methods.approve(spender, tokenId).send({from: owner});779780    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));781    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));782  });783784  itEth('Can perform transferFromCross()', async ({helper}) => {785    const collectionMinter = alice;786    const owner = bob;787    const receiver = charlie;788    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});789790    const spender = await helper.eth.createAccountWithBalance(donor, 100n);791792    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});793794    const address = helper.ethAddress.fromCollectionId(collection.collectionId);795    const contract = await helper.ethNativeContract.collection(address, 'nft');796797    await token.approve(owner, {Ethereum: spender});798799    {800      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);801      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);802      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});803      const event = result.events.Transfer;804      expect(event).to.be.like({805        address: helper.ethAddress.fromCollectionId(collection.collectionId),806        event: 'Transfer',807        returnValues: {808          from: helper.address.substrateToEth(owner.address),809          to: helper.address.substrateToEth(receiver.address),810          tokenId: token.tokenId.toString(),811        },812      });813    }814815    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});816  });817818  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {819    const owner = await helper.eth.createAccountWithBalance(donor);820    const receiver = helper.eth.createAccount();821822    const collection = await helper.nft.mintCollection(alice, {});823    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});824825    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);826827    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));828    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));829  });830});831832describe('NFT: Substrate calls', () => {833  let donor: IKeyringPair;834  let alice: IKeyringPair;835836  before(async function() {837    await usingEthPlaygrounds(async (helper, privateKey) => {838      donor = await privateKey({filename: __filename});839      [alice] = await helper.arrange.createAccounts([20n], donor);840    });841  });842843  itEth('Events emitted for mint()', async ({helper}) => {844    const collection = await helper.nft.mintCollection(alice, {});845    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);846    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');847848    const events: any = [];849    contract.events.allEvents((_: any, event: any) => {850      events.push(event);851    });852853    const {tokenId} = await collection.mintToken(alice);854    if (events.length == 0) await helper.wait.newBlocks(1);855    const event = events[0];856857    expect(event.event).to.be.equal('Transfer');858    expect(event.address).to.be.equal(collectionAddress);859    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');860    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));861    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());862  });863864  itEth('Events emitted for burn()', async ({helper}) => {865    const collection = await helper.nft.mintCollection(alice, {});866    const token = await collection.mintToken(alice);867868    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);869    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');870871    const events: any = [];872    contract.events.allEvents((_: any, event: any) => {873      events.push(event);874    });875876    await token.burn(alice);877    if (events.length == 0) await helper.wait.newBlocks(1);878    const event = events[0];879880    expect(event.event).to.be.equal('Transfer');881    expect(event.address).to.be.equal(collectionAddress);882    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));883    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');884    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());885  });886887  itEth('Events emitted for approve()', async ({helper}) => {888    const receiver = helper.eth.createAccount();889890    const collection = await helper.nft.mintCollection(alice, {});891    const token = await collection.mintToken(alice);892893    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);894    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');895896    const events: any = [];897    contract.events.allEvents((_: any, event: any) => {898      events.push(event);899    });900901    await token.approve(alice, {Ethereum: receiver});902    if (events.length == 0) await helper.wait.newBlocks(1);903    const event = events[0];904905    expect(event.event).to.be.equal('Approval');906    expect(event.address).to.be.equal(collectionAddress);907    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));908    expect(event.returnValues.approved).to.be.equal(receiver);909    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());910  });911912  itEth('Events emitted for transferFrom()', async ({helper}) => {913    const [bob] = await helper.arrange.createAccounts([10n], donor);914    const receiver = helper.eth.createAccount();915916    const collection = await helper.nft.mintCollection(alice, {});917    const token = await collection.mintToken(alice);918    await token.approve(alice, {Substrate: bob.address});919920    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);921    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');922923    const events: any = [];924    contract.events.allEvents((_: any, event: any) => {925      events.push(event);926    });927928    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});929930    if (events.length == 0) await helper.wait.newBlocks(1);931    const event = events[0];932933    expect(event.address).to.be.equal(collectionAddress);934    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));935    expect(event.returnValues.to).to.be.equal(receiver);936    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);937  });938939  itEth('Events emitted for transfer()', async ({helper}) => {940    const receiver = helper.eth.createAccount();941942    const collection = await helper.nft.mintCollection(alice, {});943    const token = await collection.mintToken(alice);944945    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);946    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');947948    const events: any = [];949    contract.events.allEvents((_: any, event: any) => {950      events.push(event);951    });952953    await token.transfer(alice, {Ethereum: receiver});954955    if (events.length == 0) await helper.wait.newBlocks(1);956    const event = events[0];957958    expect(event.address).to.be.equal(collectionAddress);959    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));960    expect(event.returnValues.to).to.be.equal(receiver);961    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);962  });963});964965describe('Common metadata', () => {966  let donor: IKeyringPair;967  let alice: IKeyringPair;968969  before(async function() {970    await usingEthPlaygrounds(async (helper, privateKey) => {971      donor = await privateKey({filename: __filename});972      [alice] = await helper.arrange.createAccounts([20n], donor);973    });974  });975976  itEth('Returns collection name', async ({helper}) => {977    const caller = await helper.eth.createAccountWithBalance(donor);978    const tokenPropertyPermissions = [{979      key: 'URI',980      permission: {981        mutable: true,982        collectionAdmin: true,983        tokenOwner: false,984      },985    }];986    const collection = await helper.nft.mintCollection(987      alice,988      {989        name: 'oh River',990        tokenPrefix: 'CHANGE',991        properties: [{key: 'ERC721Metadata', value: '1'}],992        tokenPropertyPermissions,993      },994    );995996    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);997    const name = await contract.methods.name().call();998    expect(name).to.equal('oh River');999  });10001001  itEth('Returns symbol name', async ({helper}) => {1002    const caller = await helper.eth.createAccountWithBalance(donor);1003    const tokenPropertyPermissions = [{1004      key: 'URI',1005      permission: {1006        mutable: true,1007        collectionAdmin: true,1008        tokenOwner: false,1009      },1010    }];1011    const collection = await helper.nft.mintCollection(1012      alice,1013      {1014        name: 'oh River',1015        tokenPrefix: 'CHANGE',1016        properties: [{key: 'ERC721Metadata', value: '1'}],1017        tokenPropertyPermissions,1018      },1019    );10201021    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);1022    const symbol = await contract.methods.symbol().call();1023    expect(symbol).to.equal('CHANGE');1024  });1025});10261027describe('Negative tests', () => {1028  let donor: IKeyringPair;1029  let minter: IKeyringPair;1030  let alice: IKeyringPair;10311032  before(async function() {1033    await usingEthPlaygrounds(async (helper, privateKey) => {1034      donor = await privateKey({filename: __filename});1035      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);1036    });1037  });10381039  itEth('[negative] Cant perform burn without approval', async ({helper}) => {1040    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});10411042    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1043    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10441045    const token = await collection.mintToken(minter, {Ethereum: owner});10461047    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1048    const contract = await helper.ethNativeContract.collection(address, 'nft');10491050    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1051    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;10521053    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1054    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10551056    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;1057  });10581059  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {1060    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});1061    const receiver = alice;10621063    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1064    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10651066    const token = await collection.mintToken(minter, {Ethereum: owner});10671068    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1069    const contract = await helper.ethNativeContract.collection(address, 'nft');10701071    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1072    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);10731074    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;10751076    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1077    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10781079    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1080  });1081});