git.delta.rocks / unique-network / refs/commits / 55aed0decfd1

difftreelog

source

tests/src/eth/nonFungible.test.ts43.1 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {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  // TODO combine all minting tests in one place92  [93    'substrate' as const,94    'ethereum' as const,95  ].map(testCase => {96    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {97      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);9899      const receiverEth = helper.eth.createAccount();100      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);101      const receiverSub = bob;102      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);103104      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);105      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });106      const permissions: ITokenPropertyPermission[] = properties107        .map(p => {108          return {109            key: p.key, permission: {110              tokenOwner: false,111              collectionAdmin: true,112              mutable: false,113            },114          };115        });116117      const collection = await helper.nft.mintCollection(minter, {118        tokenPrefix: 'ethp',119        tokenPropertyPermissions: permissions,120      });121      await collection.addAdmin(minter, {Ethereum: collectionAdmin});122123      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);124      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);125      let expectedTokenId = await contract.methods.nextTokenId().call();126      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();127      let tokenId = result.events.Transfer.returnValues.tokenId;128      expect(tokenId).to.be.equal(expectedTokenId);129130      let event = result.events.Transfer;131      expect(event.address).to.be.equal(collectionAddress);132      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');133      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));134      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);135136      expectedTokenId = await contract.methods.nextTokenId().call();137      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();138      event = result.events.Transfer;139      expect(event.address).to.be.equal(collectionAddress);140      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');141      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));142      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);143144      tokenId = result.events.Transfer.returnValues.tokenId;145146      expect(tokenId).to.be.equal(expectedTokenId);147148      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties149        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));150151      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))152        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});153    });154  });155156  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {157    const nonOwner = await helper.eth.createAccountWithBalance(donor);158    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);159160    const collection = await helper.nft.mintCollection(minter);161    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);162    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');163164    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))165      .to.be.rejectedWith('PublicMintingNotAllowed');166  });167168  //TODO: CORE-302 add eth methods169  itEth.skip('Can perform mintBulk()', async ({helper}) => {170    const caller = await helper.eth.createAccountWithBalance(donor);171    const receiver = helper.eth.createAccount();172173    const collection = await helper.nft.mintCollection(minter);174    await collection.addAdmin(minter, {Ethereum: caller});175176    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);177    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);178    {179      const bulkSize = 3;180      const nextTokenId = await contract.methods.nextTokenId().call();181      expect(nextTokenId).to.be.equal('1');182      const result = await contract.methods.mintBulkWithTokenURI(183        receiver,184        Array.from({length: bulkSize}, (_, i) => (185          [+nextTokenId + i, `Test URI ${i}`]186        )),187      ).send({from: caller});188189      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);190      for (let i = 0; i < bulkSize; i++) {191        const event = events[i];192        expect(event.address).to.equal(collectionAddress);193        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');194        expect(event.returnValues.to).to.equal(receiver);195        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);196197        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);198      }199    }200  });201202  itEth('Can perform burn()', async ({helper}) => {203    const caller = await helper.eth.createAccountWithBalance(donor);204205    const collection = await helper.nft.mintCollection(minter, {});206    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});207208    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);209    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);210211    {212      const result = await contract.methods.burn(tokenId).send({from: caller});213214      const event = result.events.Transfer;215      expect(event.address).to.be.equal(collectionAddress);216      expect(event.returnValues.from).to.be.equal(caller);217      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');218      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);219    }220  });221222  itEth('Can perform approve()', async ({helper}) => {223    const owner = await helper.eth.createAccountWithBalance(donor);224    const spender = helper.eth.createAccount();225226    const collection = await helper.nft.mintCollection(minter, {});227    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});228229    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);230    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);231232    {233      const result = await contract.methods.approve(spender, tokenId).send({from: owner});234235      const event = result.events.Approval;236      expect(event.address).to.be.equal(collectionAddress);237      expect(event.returnValues.owner).to.be.equal(owner);238      expect(event.returnValues.approved).to.be.equal(spender);239      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);240    }241  });242243  itEth('Can perform setApprovalForAll()', async ({helper}) => {244    const owner = await helper.eth.createAccountWithBalance(donor);245    const operator = helper.eth.createAccount();246247    const collection = await helper.nft.mintCollection(minter, {});248249    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);250    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);251252    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();253    expect(approvedBefore).to.be.equal(false);254255    {256      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});257258      expect(result.events.ApprovalForAll).to.be.like({259        address: collectionAddress,260        event: 'ApprovalForAll',261        returnValues: {262          owner,263          operator,264          approved: true,265        },266      });267268      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();269      expect(approvedAfter).to.be.equal(true);270    }271272    {273      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});274275      expect(result.events.ApprovalForAll).to.be.like({276        address: collectionAddress,277        event: 'ApprovalForAll',278        returnValues: {279          owner,280          operator,281          approved: false,282        },283      });284285      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();286      expect(approvedAfter).to.be.equal(false);287    }288  });289290  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {291    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});292293    const owner = await helper.eth.createAccountWithBalance(donor);294    const operator = await helper.eth.createAccountWithBalance(donor, 100n);295296    const token = await collection.mintToken(minter, {Ethereum: owner});297298    const address = helper.ethAddress.fromCollectionId(collection.collectionId);299    const contract = await helper.ethNativeContract.collection(address, 'nft');300301    {302      await contract.methods.setApprovalForAll(operator, true).send({from: owner});303      const ownerCross = helper.ethCrossAccount.fromAddress(owner);304      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});305      const events = result.events.Transfer;306307      expect(events).to.be.like({308        address,309        event: 'Transfer',310        returnValues: {311          from: owner,312          to: '0x0000000000000000000000000000000000000000',313          tokenId: token.tokenId.toString(),314        },315      });316    }317318    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;319  });320321  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {322    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});323324    const owner = await helper.eth.createAccountWithBalance(donor);325    const operator = await helper.eth.createAccountWithBalance(donor);326    const receiver = charlie;327328    const token = await collection.mintToken(minter, {Ethereum: owner});329330    const address = helper.ethAddress.fromCollectionId(collection.collectionId);331    const contract = await helper.ethNativeContract.collection(address, 'nft');332333    {334      await contract.methods.setApprovalForAll(operator, true).send({from: owner});335      const ownerCross = helper.ethCrossAccount.fromAddress(owner);336      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);337      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});338      const event = result.events.Transfer;339      expect(event).to.be.like({340        address: helper.ethAddress.fromCollectionId(collection.collectionId),341        event: 'Transfer',342        returnValues: {343          from: owner,344          to: helper.address.substrateToEth(receiver.address),345          tokenId: token.tokenId.toString(),346        },347      });348    }349350    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});351  });352353  itEth('Can perform burnFromCross()', async ({helper}) => {354    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});355    const ownerSub = bob;356    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);357    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);358    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);359360    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);361    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);362363    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});364    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});365366    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);367    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');368369    // Approve tokens from substrate and ethereum:370    await token1.approve(ownerSub, {Ethereum: burnerEth});371    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});372373    // can burnFromCross:374    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});375    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});376    const events1 = result1.events.Transfer;377    const events2 = result2.events.Transfer;378379    // Check events for burnFromCross (substrate and ethereum):380    [381      [events1, token1, helper.address.substrateToEth(ownerSub.address)],382      [events2, token2, ownerEth],383    ].map(burnData => {384      expect(burnData[0]).to.be.like({385        address: collectionAddress,386        event: 'Transfer',387        returnValues: {388          from: burnData[2],389          to: '0x0000000000000000000000000000000000000000',390          tokenId: burnData[1].tokenId.toString(),391        },392      });393    });394395    expect(await token1.doesExist()).to.be.false;396    expect(await token2.doesExist()).to.be.false;397  });398399  // TODO combine all approve tests in one place400  itEth('Can perform approveCross()', async ({helper}) => {401    // arrange: create accounts402    const owner = await helper.eth.createAccountWithBalance(donor, 100n);403    const ownerCross = helper.ethCrossAccount.fromAddress(owner);404    const receiverSub = charlie;405    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);406    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);407    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);408409    // arrange: create collection and tokens:410    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});411    const token1 = await collection.mintToken(minter, {Ethereum: owner});412    const token2 = await collection.mintToken(minter, {Ethereum: owner});413414    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');415416    // Can approveCross substrate and ethereum address:417    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});418    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});419    const eventSub = resultSub.events.Approval;420    const eventEth = resultEth.events.Approval;421    expect(eventSub).to.be.like({422      address: helper.ethAddress.fromCollectionId(collection.collectionId),423      event: 'Approval',424      returnValues: {425        owner,426        approved: helper.address.substrateToEth(receiverSub.address),427        tokenId: token1.tokenId.toString(),428      },429    });430    expect(eventEth).to.be.like({431      address: helper.ethAddress.fromCollectionId(collection.collectionId),432      event: 'Approval',433      returnValues: {434        owner,435        approved: receiverEth,436        tokenId: token2.tokenId.toString(),437      },438    });439440    // Substrate address can transferFrom approved tokens:441    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});442    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});443    // Ethereum address can transferFromCross approved tokens:444    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});445    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});446  });447448  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {449    const nonOwner = await helper.eth.createAccountWithBalance(donor);450    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);451    const owner = await helper.eth.createAccountWithBalance(donor);452    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});453    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');454    const token = await collection.mintToken(minter, {Ethereum: owner});455456    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');457  });458459  itEth('Can reaffirm approved address', async ({helper}) => {460    const owner = await helper.eth.createAccountWithBalance(donor, 100n);461    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);462    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);463    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);464    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);465    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});466    const token1 = await collection.mintToken(minter, {Ethereum: owner});467    const token2 = await collection.mintToken(minter, {Ethereum: owner});468    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');469470    // Can approve and reaffirm approved address:471    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});472    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});473474    // receiver1 cannot transferFrom:475    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;476    // receiver2 can transferFrom:477    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});478479    // can set approved address to self address to remove approval:480    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});481    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});482483    // receiver1 cannot transfer token anymore:484    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;485  });486487  itEth('Can perform transferFrom()', async ({helper}) => {488    const owner = await helper.eth.createAccountWithBalance(donor);489    const spender = await helper.eth.createAccountWithBalance(donor);490    const receiver = helper.eth.createAccount();491492    const collection = await helper.nft.mintCollection(minter, {});493    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});494495    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);496    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);497498    await contract.methods.approve(spender, tokenId).send({from: owner});499500    {501      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});502503      const event = result.events.Transfer;504      expect(event.address).to.be.equal(collectionAddress);505      expect(event.returnValues.from).to.be.equal(owner);506      expect(event.returnValues.to).to.be.equal(receiver);507      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);508    }509510    {511      const balance = await contract.methods.balanceOf(receiver).call();512      expect(+balance).to.equal(1);513    }514515    {516      const balance = await contract.methods.balanceOf(owner).call();517      expect(+balance).to.equal(0);518    }519  });520521  itEth('Can perform transferFromCross()', async ({helper}) => {522    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});523524    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);525    const spender = await helper.eth.createAccountWithBalance(donor);526527    const token = await collection.mintToken(minter, {Substrate: owner.address});528529    const address = helper.ethAddress.fromCollectionId(collection.collectionId);530    const contract = await helper.ethNativeContract.collection(address, 'nft');531532    await token.approve(owner, {Ethereum: spender});533534    {535      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);536      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);537      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});538      const event = result.events.Transfer;539      expect(event).to.be.like({540        address: helper.ethAddress.fromCollectionId(collection.collectionId),541        event: 'Transfer',542        returnValues: {543          from: helper.address.substrateToEth(owner.address),544          to: helper.address.substrateToEth(receiver.address),545          tokenId: token.tokenId.toString(),546        },547      });548    }549550    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});551  });552553  itEth('Can perform transfer()', async ({helper}) => {554    const collection = await helper.nft.mintCollection(minter, {});555    const owner = await helper.eth.createAccountWithBalance(donor);556    const receiver = helper.eth.createAccount();557558    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});559560    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);561    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);562563    {564      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});565566      const event = result.events.Transfer;567      expect(event.address).to.be.equal(collectionAddress);568      expect(event.returnValues.from).to.be.equal(owner);569      expect(event.returnValues.to).to.be.equal(receiver);570      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);571    }572573    {574      const balance = await contract.methods.balanceOf(owner).call();575      expect(+balance).to.equal(0);576    }577578    {579      const balance = await contract.methods.balanceOf(receiver).call();580      expect(+balance).to.equal(1);581    }582  });583584  itEth('Can perform transferCross()', async ({helper}) => {585    const collection = await helper.nft.mintCollection(minter, {});586    const owner = await helper.eth.createAccountWithBalance(donor);587    const receiverEth = await helper.eth.createAccountWithBalance(donor);588    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);589    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);590591    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});592593    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);594    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);595596    {597      // Can transferCross to ethereum address:598      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});599      // Check events:600      const event = result.events.Transfer;601      expect(event.address).to.be.equal(collectionAddress);602      expect(event.returnValues.from).to.be.equal(owner);603      expect(event.returnValues.to).to.be.equal(receiverEth);604      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);605606      // owner has balance = 0:607      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();608      expect(+ownerBalance).to.equal(0);609      // receiver owns token:610      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();611      expect(+receiverBalance).to.equal(1);612      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});613    }614615    {616      // Can transferCross to substrate address:617      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});618      // Check events:619      const event = substrateResult.events.Transfer;620      expect(event.address).to.be.equal(collectionAddress);621      expect(event.returnValues.from).to.be.equal(receiverEth);622      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));623      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);624625      // owner has balance = 0:626      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();627      expect(+ownerBalance).to.equal(0);628      // receiver owns token:629      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});630      expect(receiverBalance).to.contain(tokenId);631    }632  });633634  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {635    const sender = await helper.eth.createAccountWithBalance(donor);636    const tokenOwner = await helper.eth.createAccountWithBalance(donor);637    const receiverSub = minter;638    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);639640    const collection = await helper.nft.mintCollection(minter, {});641    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);642    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);643644    await collection.mintToken(minter, {Ethereum: sender});645    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});646647    // Cannot transferCross someone else's token:648    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;649    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;650    // Cannot transfer token if it does not exist:651    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;652  }));653});654655describe('NFT: Fees', () => {656  let donor: IKeyringPair;657  let alice: IKeyringPair;658  let bob: IKeyringPair;659  let charlie: IKeyringPair;660661  before(async function() {662    await usingEthPlaygrounds(async (helper, privateKey) => {663      donor = await privateKey({filename: __filename});664      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);665    });666  });667668  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {669    const owner = await helper.eth.createAccountWithBalance(donor);670    const spender = helper.eth.createAccount();671672    const collection = await helper.nft.mintCollection(alice, {});673    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});674675    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);676677    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));678    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));679  });680681  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {682    const owner = await helper.eth.createAccountWithBalance(donor);683    const spender = await helper.eth.createAccountWithBalance(donor);684685    const collection = await helper.nft.mintCollection(alice, {});686    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});687688    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);689690    await contract.methods.approve(spender, tokenId).send({from: owner});691692    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));693    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));694  });695696  itEth('Can perform transferFromCross()', async ({helper}) => {697    const collectionMinter = alice;698    const owner = bob;699    const receiver = charlie;700    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});701702    const spender = await helper.eth.createAccountWithBalance(donor, 100n);703704    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});705706    const address = helper.ethAddress.fromCollectionId(collection.collectionId);707    const contract = await helper.ethNativeContract.collection(address, 'nft');708709    await token.approve(owner, {Ethereum: spender});710711    {712      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);713      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);714      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});715      const event = result.events.Transfer;716      expect(event).to.be.like({717        address: helper.ethAddress.fromCollectionId(collection.collectionId),718        event: 'Transfer',719        returnValues: {720          from: helper.address.substrateToEth(owner.address),721          to: helper.address.substrateToEth(receiver.address),722          tokenId: token.tokenId.toString(),723        },724      });725    }726727    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});728  });729730  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {731    const owner = await helper.eth.createAccountWithBalance(donor);732    const receiver = helper.eth.createAccount();733734    const collection = await helper.nft.mintCollection(alice, {});735    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});736737    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);738739    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));740    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));741  });742});743744describe('NFT: Substrate calls', () => {745  let donor: IKeyringPair;746  let alice: IKeyringPair;747748  before(async function() {749    await usingEthPlaygrounds(async (helper, privateKey) => {750      donor = await privateKey({filename: __filename});751      [alice] = await helper.arrange.createAccounts([20n], donor);752    });753  });754755  itEth('Events emitted for mint()', async ({helper}) => {756    const collection = await helper.nft.mintCollection(alice, {});757    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);758    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');759760    const events: any = [];761    contract.events.allEvents((_: any, event: any) => {762      events.push(event);763    });764765    const {tokenId} = await collection.mintToken(alice);766    if (events.length == 0) await helper.wait.newBlocks(1);767    const event = events[0];768769    expect(event.event).to.be.equal('Transfer');770    expect(event.address).to.be.equal(collectionAddress);771    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');772    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));773    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());774  });775776  itEth('Events emitted for burn()', async ({helper}) => {777    const collection = await helper.nft.mintCollection(alice, {});778    const token = await collection.mintToken(alice);779780    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);781    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');782783    const events: any = [];784    contract.events.allEvents((_: any, event: any) => {785      events.push(event);786    });787788    await token.burn(alice);789    if (events.length == 0) await helper.wait.newBlocks(1);790    const event = events[0];791792    expect(event.event).to.be.equal('Transfer');793    expect(event.address).to.be.equal(collectionAddress);794    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));795    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');796    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());797  });798799  itEth('Events emitted for approve()', async ({helper}) => {800    const receiver = helper.eth.createAccount();801802    const collection = await helper.nft.mintCollection(alice, {});803    const token = await collection.mintToken(alice);804805    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);806    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');807808    const events: any = [];809    contract.events.allEvents((_: any, event: any) => {810      events.push(event);811    });812813    await token.approve(alice, {Ethereum: receiver});814    if (events.length == 0) await helper.wait.newBlocks(1);815    const event = events[0];816817    expect(event.event).to.be.equal('Approval');818    expect(event.address).to.be.equal(collectionAddress);819    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));820    expect(event.returnValues.approved).to.be.equal(receiver);821    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());822  });823824  itEth('Events emitted for transferFrom()', async ({helper}) => {825    const [bob] = await helper.arrange.createAccounts([10n], donor);826    const receiver = helper.eth.createAccount();827828    const collection = await helper.nft.mintCollection(alice, {});829    const token = await collection.mintToken(alice);830    await token.approve(alice, {Substrate: bob.address});831832    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);833    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');834835    const events: any = [];836    contract.events.allEvents((_: any, event: any) => {837      events.push(event);838    });839840    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});841842    if (events.length == 0) await helper.wait.newBlocks(1);843    const event = events[0];844845    expect(event.address).to.be.equal(collectionAddress);846    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));847    expect(event.returnValues.to).to.be.equal(receiver);848    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);849  });850851  itEth('Events emitted for transfer()', async ({helper}) => {852    const receiver = helper.eth.createAccount();853854    const collection = await helper.nft.mintCollection(alice, {});855    const token = await collection.mintToken(alice);856857    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);858    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');859860    const events: any = [];861    contract.events.allEvents((_: any, event: any) => {862      events.push(event);863    });864865    await token.transfer(alice, {Ethereum: receiver});866867    if (events.length == 0) await helper.wait.newBlocks(1);868    const event = events[0];869870    expect(event.address).to.be.equal(collectionAddress);871    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));872    expect(event.returnValues.to).to.be.equal(receiver);873    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);874  });875});876877describe('Common metadata', () => {878  let donor: IKeyringPair;879  let alice: IKeyringPair;880881  before(async function() {882    await usingEthPlaygrounds(async (helper, privateKey) => {883      donor = await privateKey({filename: __filename});884      [alice] = await helper.arrange.createAccounts([20n], donor);885    });886  });887888  itEth('Returns collection name', async ({helper}) => {889    const caller = await helper.eth.createAccountWithBalance(donor);890    const tokenPropertyPermissions = [{891      key: 'URI',892      permission: {893        mutable: true,894        collectionAdmin: true,895        tokenOwner: false,896      },897    }];898    const collection = await helper.nft.mintCollection(899      alice,900      {901        name: 'oh River',902        tokenPrefix: 'CHANGE',903        properties: [{key: 'ERC721Metadata', value: '1'}],904        tokenPropertyPermissions,905      },906    );907908    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);909    const name = await contract.methods.name().call();910    expect(name).to.equal('oh River');911  });912913  itEth('Returns symbol name', async ({helper}) => {914    const caller = await helper.eth.createAccountWithBalance(donor);915    const tokenPropertyPermissions = [{916      key: 'URI',917      permission: {918        mutable: true,919        collectionAdmin: true,920        tokenOwner: false,921      },922    }];923    const collection = await helper.nft.mintCollection(924      alice,925      {926        name: 'oh River',927        tokenPrefix: 'CHANGE',928        properties: [{key: 'ERC721Metadata', value: '1'}],929        tokenPropertyPermissions,930      },931    );932933    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);934    const symbol = await contract.methods.symbol().call();935    expect(symbol).to.equal('CHANGE');936  });937});938939describe('Negative tests', () => {940  let donor: IKeyringPair;941  let minter: IKeyringPair;942  let alice: IKeyringPair;943944  before(async function() {945    await usingEthPlaygrounds(async (helper, privateKey) => {946      donor = await privateKey({filename: __filename});947      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);948    });949  });950951  itEth('[negative] Cant perform burn without approval', async ({helper}) => {952    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});953954    const owner = await helper.eth.createAccountWithBalance(donor, 100n);955    const spender = await helper.eth.createAccountWithBalance(donor, 100n);956957    const token = await collection.mintToken(minter, {Ethereum: owner});958959    const address = helper.ethAddress.fromCollectionId(collection.collectionId);960    const contract = await helper.ethNativeContract.collection(address, 'nft');961962    const ownerCross = helper.ethCrossAccount.fromAddress(owner);963    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;964965    await contract.methods.setApprovalForAll(spender, true).send({from: owner});966    await contract.methods.setApprovalForAll(spender, false).send({from: owner});967968    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;969  });970971  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {972    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});973    const receiver = alice;974975    const owner = await helper.eth.createAccountWithBalance(donor, 100n);976    const spender = await helper.eth.createAccountWithBalance(donor, 100n);977978    const token = await collection.mintToken(minter, {Ethereum: owner});979980    const address = helper.ethAddress.fromCollectionId(collection.collectionId);981    const contract = await helper.ethNativeContract.collection(address, 'nft');982983    const ownerCross = helper.ethCrossAccount.fromAddress(owner);984    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);985986    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;987988    await contract.methods.setApprovalForAll(spender, true).send({from: owner});989    await contract.methods.setApprovalForAll(spender, false).send({from: owner});990991    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;992  });993});