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

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    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);171    // TODO: this wont work right now, need release 919000 first172    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();173    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();174    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);175  });176177  // TODO combine all minting tests in one place178  [179    'substrate' as const,180    'ethereum' as const,181  ].map(testCase => {182    itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {183      const collectionAdmin = await helper.eth.createAccountWithBalance(donor);184185      const receiverEth = helper.eth.createAccount();186      const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);187      const receiverSub = bob;188      const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);189190      // const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);191      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });192      const permissions: ITokenPropertyPermission[] = properties193        .map(p => {194          return {195            key: p.key, permission: {196              tokenOwner: false,197              collectionAdmin: true,198              mutable: false,199            },200          };201        });202203      const collection = await helper.nft.mintCollection(minter, {204        tokenPrefix: 'ethp',205        tokenPropertyPermissions: permissions,206      });207      await collection.addAdmin(minter, {Ethereum: collectionAdmin});208209      const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);210      const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);211      let expectedTokenId = await contract.methods.nextTokenId().call();212      let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();213      let tokenId = result.events.Transfer.returnValues.tokenId;214      expect(tokenId).to.be.equal(expectedTokenId);215216      let event = result.events.Transfer;217      expect(event.address).to.be.equal(collectionAddress);218      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');219      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));220      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);221222      expectedTokenId = await contract.methods.nextTokenId().call();223      result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();224      event = result.events.Transfer;225      expect(event.address).to.be.equal(collectionAddress);226      expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');227      expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));228      expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);229230      tokenId = result.events.Transfer.returnValues.tokenId;231232      expect(tokenId).to.be.equal(expectedTokenId);233234      expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties235        .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));236237      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))238        .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});239    });240  });241242  itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {243    const nonOwner = await helper.eth.createAccountWithBalance(donor);244    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);245246    const collection = await helper.nft.mintCollection(minter);247    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);248    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');249250    await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))251      .to.be.rejectedWith('PublicMintingNotAllowed');252  });253254  //TODO: CORE-302 add eth methods255  itEth.skip('Can perform mintBulk()', async ({helper}) => {256    const caller = await helper.eth.createAccountWithBalance(donor);257    const receiver = helper.eth.createAccount();258259    const collection = await helper.nft.mintCollection(minter);260    await collection.addAdmin(minter, {Ethereum: caller});261262    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);263    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);264    {265      const bulkSize = 3;266      const nextTokenId = await contract.methods.nextTokenId().call();267      expect(nextTokenId).to.be.equal('1');268      const result = await contract.methods.mintBulkWithTokenURI(269        receiver,270        Array.from({length: bulkSize}, (_, i) => (271          [+nextTokenId + i, `Test URI ${i}`]272        )),273      ).send({from: caller});274275      const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);276      for (let i = 0; i < bulkSize; i++) {277        const event = events[i];278        expect(event.address).to.equal(collectionAddress);279        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');280        expect(event.returnValues.to).to.equal(receiver);281        expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);282283        expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);284      }285    }286  });287288  itEth('Can perform burn()', async ({helper}) => {289    const caller = await helper.eth.createAccountWithBalance(donor);290291    const collection = await helper.nft.mintCollection(minter, {});292    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});293294    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);295    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);296297    {298      const result = await contract.methods.burn(tokenId).send({from: caller});299300      const event = result.events.Transfer;301      expect(event.address).to.be.equal(collectionAddress);302      expect(event.returnValues.from).to.be.equal(caller);303      expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');304      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);305    }306  });307308  itEth('Can perform approve()', async ({helper}) => {309    const owner = await helper.eth.createAccountWithBalance(donor);310    const spender = helper.eth.createAccount();311312    const collection = await helper.nft.mintCollection(minter, {});313    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});314315    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);316    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317318    {319      const result = await contract.methods.approve(spender, tokenId).send({from: owner});320321      const event = result.events.Approval;322      expect(event.address).to.be.equal(collectionAddress);323      expect(event.returnValues.owner).to.be.equal(owner);324      expect(event.returnValues.approved).to.be.equal(spender);325      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);326    }327  });328329  itEth('Can perform setApprovalForAll()', async ({helper}) => {330    const owner = await helper.eth.createAccountWithBalance(donor);331    const operator = helper.eth.createAccount();332333    const collection = await helper.nft.mintCollection(minter, {});334335    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);336    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);337338    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();339    expect(approvedBefore).to.be.equal(false);340341    {342      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});343344      expect(result.events.ApprovalForAll).to.be.like({345        address: collectionAddress,346        event: 'ApprovalForAll',347        returnValues: {348          owner,349          operator,350          approved: true,351        },352      });353354      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();355      expect(approvedAfter).to.be.equal(true);356    }357358    {359      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});360361      expect(result.events.ApprovalForAll).to.be.like({362        address: collectionAddress,363        event: 'ApprovalForAll',364        returnValues: {365          owner,366          operator,367          approved: false,368        },369      });370371      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();372      expect(approvedAfter).to.be.equal(false);373    }374  });375376  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {377    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});378379    const owner = await helper.eth.createAccountWithBalance(donor);380    const operator = await helper.eth.createAccountWithBalance(donor, 100n);381382    const token = await collection.mintToken(minter, {Ethereum: owner});383384    const address = helper.ethAddress.fromCollectionId(collection.collectionId);385    const contract = await helper.ethNativeContract.collection(address, 'nft');386387    {388      await contract.methods.setApprovalForAll(operator, true).send({from: owner});389      const ownerCross = helper.ethCrossAccount.fromAddress(owner);390      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});391      const events = result.events.Transfer;392393      expect(events).to.be.like({394        address,395        event: 'Transfer',396        returnValues: {397          from: owner,398          to: '0x0000000000000000000000000000000000000000',399          tokenId: token.tokenId.toString(),400        },401      });402    }403404    expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;405  });406407  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {408    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});409410    const owner = await helper.eth.createAccountWithBalance(donor);411    const operator = await helper.eth.createAccountWithBalance(donor);412    const receiver = charlie;413414    const token = await collection.mintToken(minter, {Ethereum: owner});415416    const address = helper.ethAddress.fromCollectionId(collection.collectionId);417    const contract = await helper.ethNativeContract.collection(address, 'nft');418419    {420      await contract.methods.setApprovalForAll(operator, true).send({from: owner});421      const ownerCross = helper.ethCrossAccount.fromAddress(owner);422      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);423      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});424      const event = result.events.Transfer;425      expect(event).to.be.like({426        address: helper.ethAddress.fromCollectionId(collection.collectionId),427        event: 'Transfer',428        returnValues: {429          from: owner,430          to: helper.address.substrateToEth(receiver.address),431          tokenId: token.tokenId.toString(),432        },433      });434    }435436    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});437  });438439  itEth('Can perform burnFromCross()', async ({helper}) => {440    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});441    const ownerSub = bob;442    const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);443    const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);444    const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);445446    const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);447    const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);448449    const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});450    const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});451452    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);453    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');454455    // Approve tokens from substrate and ethereum:456    await token1.approve(ownerSub, {Ethereum: burnerEth});457    await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});458459    // can burnFromCross:460    const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});461    const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});462    const events1 = result1.events.Transfer;463    const events2 = result2.events.Transfer;464465    // Check events for burnFromCross (substrate and ethereum):466    [467      [events1, token1, helper.address.substrateToEth(ownerSub.address)],468      [events2, token2, ownerEth],469    ].map(burnData => {470      expect(burnData[0]).to.be.like({471        address: collectionAddress,472        event: 'Transfer',473        returnValues: {474          from: burnData[2],475          to: '0x0000000000000000000000000000000000000000',476          tokenId: burnData[1].tokenId.toString(),477        },478      });479    });480481    expect(await token1.doesExist()).to.be.false;482    expect(await token2.doesExist()).to.be.false;483  });484485  // TODO combine all approve tests in one place486  itEth('Can perform approveCross()', async ({helper}) => {487    // arrange: create accounts488    const owner = await helper.eth.createAccountWithBalance(donor, 100n);489    const ownerCross = helper.ethCrossAccount.fromAddress(owner);490    const receiverSub = charlie;491    const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);492    const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);493    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);494495    // arrange: create collection and tokens:496    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});497    const token1 = await collection.mintToken(minter, {Ethereum: owner});498    const token2 = await collection.mintToken(minter, {Ethereum: owner});499500    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');501502    // Can approveCross substrate and ethereum address:503    const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});504    const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});505    const eventSub = resultSub.events.Approval;506    const eventEth = resultEth.events.Approval;507    expect(eventSub).to.be.like({508      address: helper.ethAddress.fromCollectionId(collection.collectionId),509      event: 'Approval',510      returnValues: {511        owner,512        approved: helper.address.substrateToEth(receiverSub.address),513        tokenId: token1.tokenId.toString(),514      },515    });516    expect(eventEth).to.be.like({517      address: helper.ethAddress.fromCollectionId(collection.collectionId),518      event: 'Approval',519      returnValues: {520        owner,521        approved: receiverEth,522        tokenId: token2.tokenId.toString(),523      },524    });525526    // Substrate address can transferFrom approved tokens:527    await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});528    expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});529    // Ethereum address can transferFromCross approved tokens:530    await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});531    expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});532  });533534  itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {535    const nonOwner = await helper.eth.createAccountWithBalance(donor);536    const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);537    const owner = await helper.eth.createAccountWithBalance(donor);538    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});539    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');540    const token = await collection.mintToken(minter, {Ethereum: owner});541542    await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');543  });544545  itEth('Can reaffirm approved address', async ({helper}) => {546    const owner = await helper.eth.createAccountWithBalance(donor, 100n);547    const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);548    const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);549    const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);550    const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);551    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});552    const token1 = await collection.mintToken(minter, {Ethereum: owner});553    const token2 = await collection.mintToken(minter, {Ethereum: owner});554    const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');555556    // Can approve and reaffirm approved address:557    await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});558    await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});559560    // receiver1 cannot transferFrom:561    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;562    // receiver2 can transferFrom:563    await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});564565    // can set approved address to self address to remove approval:566    await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});567    await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});568569    // receiver1 cannot transfer token anymore:570    await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;571  });572573  itEth('Can perform transferFrom()', async ({helper}) => {574    const owner = await helper.eth.createAccountWithBalance(donor);575    const spender = await helper.eth.createAccountWithBalance(donor);576    const receiver = helper.eth.createAccount();577578    const collection = await helper.nft.mintCollection(minter, {});579    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});580581    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);582    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);583584    await contract.methods.approve(spender, tokenId).send({from: owner});585586    {587      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});588589      const event = result.events.Transfer;590      expect(event.address).to.be.equal(collectionAddress);591      expect(event.returnValues.from).to.be.equal(owner);592      expect(event.returnValues.to).to.be.equal(receiver);593      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);594    }595596    {597      const balance = await contract.methods.balanceOf(receiver).call();598      expect(+balance).to.equal(1);599    }600601    {602      const balance = await contract.methods.balanceOf(owner).call();603      expect(+balance).to.equal(0);604    }605  });606607  itEth('Can perform transferFromCross()', async ({helper}) => {608    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});609610    const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);611    const spender = await helper.eth.createAccountWithBalance(donor);612613    const token = await collection.mintToken(minter, {Substrate: owner.address});614615    const address = helper.ethAddress.fromCollectionId(collection.collectionId);616    const contract = await helper.ethNativeContract.collection(address, 'nft');617618    await token.approve(owner, {Ethereum: spender});619620    {621      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);622      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);623      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});624      const event = result.events.Transfer;625      expect(event).to.be.like({626        address: helper.ethAddress.fromCollectionId(collection.collectionId),627        event: 'Transfer',628        returnValues: {629          from: helper.address.substrateToEth(owner.address),630          to: helper.address.substrateToEth(receiver.address),631          tokenId: token.tokenId.toString(),632        },633      });634    }635636    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});637  });638639  itEth('Can perform transfer()', async ({helper}) => {640    const collection = await helper.nft.mintCollection(minter, {});641    const owner = await helper.eth.createAccountWithBalance(donor);642    const receiver = helper.eth.createAccount();643644    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});645646    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);647    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);648649    {650      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});651652      const event = result.events.Transfer;653      expect(event.address).to.be.equal(collectionAddress);654      expect(event.returnValues.from).to.be.equal(owner);655      expect(event.returnValues.to).to.be.equal(receiver);656      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);657    }658659    {660      const balance = await contract.methods.balanceOf(owner).call();661      expect(+balance).to.equal(0);662    }663664    {665      const balance = await contract.methods.balanceOf(receiver).call();666      expect(+balance).to.equal(1);667    }668  });669670  itEth('Can perform transferCross()', async ({helper}) => {671    const collection = await helper.nft.mintCollection(minter, {});672    const owner = await helper.eth.createAccountWithBalance(donor);673    const receiverEth = await helper.eth.createAccountWithBalance(donor);674    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);675    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);676677    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});678679    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);680    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);681682    {683      // Can transferCross to ethereum address:684      const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});685      // Check events:686      const event = result.events.Transfer;687      expect(event.address).to.be.equal(collectionAddress);688      expect(event.returnValues.from).to.be.equal(owner);689      expect(event.returnValues.to).to.be.equal(receiverEth);690      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);691692      // owner has balance = 0:693      const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();694      expect(+ownerBalance).to.equal(0);695      // receiver owns token:696      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();697      expect(+receiverBalance).to.equal(1);698      expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});699    }700701    {702      // Can transferCross to substrate address:703      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});704      // Check events:705      const event = substrateResult.events.Transfer;706      expect(event.address).to.be.equal(collectionAddress);707      expect(event.returnValues.from).to.be.equal(receiverEth);708      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));709      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);710711      // owner has balance = 0:712      const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();713      expect(+ownerBalance).to.equal(0);714      // receiver owns token:715      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});716      expect(receiverBalance).to.contain(tokenId);717    }718  });719720  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {721    const sender = await helper.eth.createAccountWithBalance(donor);722    const tokenOwner = await helper.eth.createAccountWithBalance(donor);723    const receiverSub = minter;724    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);725726    const collection = await helper.nft.mintCollection(minter, {});727    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);728    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);729730    await collection.mintToken(minter, {Ethereum: sender});731    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});732733    // Cannot transferCross someone else's token:734    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;735    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;736    // Cannot transfer token if it does not exist:737    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;738  }));739});740741describe('NFT: Fees', () => {742  let donor: IKeyringPair;743  let alice: IKeyringPair;744  let bob: IKeyringPair;745  let charlie: IKeyringPair;746747  before(async function() {748    await usingEthPlaygrounds(async (helper, privateKey) => {749      donor = await privateKey({filename: __filename});750      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);751    });752  });753754  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {755    const owner = await helper.eth.createAccountWithBalance(donor);756    const spender = helper.eth.createAccount();757758    const collection = await helper.nft.mintCollection(alice, {});759    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});760761    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);762763    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));764    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));765  });766767  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {768    const owner = await helper.eth.createAccountWithBalance(donor);769    const spender = await helper.eth.createAccountWithBalance(donor);770771    const collection = await helper.nft.mintCollection(alice, {});772    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});773774    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);775776    await contract.methods.approve(spender, tokenId).send({from: owner});777778    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));779    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));780  });781782  itEth('Can perform transferFromCross()', async ({helper}) => {783    const collectionMinter = alice;784    const owner = bob;785    const receiver = charlie;786    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});787788    const spender = await helper.eth.createAccountWithBalance(donor, 100n);789790    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});791792    const address = helper.ethAddress.fromCollectionId(collection.collectionId);793    const contract = await helper.ethNativeContract.collection(address, 'nft');794795    await token.approve(owner, {Ethereum: spender});796797    {798      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);799      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);800      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});801      const event = result.events.Transfer;802      expect(event).to.be.like({803        address: helper.ethAddress.fromCollectionId(collection.collectionId),804        event: 'Transfer',805        returnValues: {806          from: helper.address.substrateToEth(owner.address),807          to: helper.address.substrateToEth(receiver.address),808          tokenId: token.tokenId.toString(),809        },810      });811    }812813    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});814  });815816  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {817    const owner = await helper.eth.createAccountWithBalance(donor);818    const receiver = helper.eth.createAccount();819820    const collection = await helper.nft.mintCollection(alice, {});821    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});822823    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);824825    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));826    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));827  });828});829830describe('NFT: Substrate calls', () => {831  let donor: IKeyringPair;832  let alice: IKeyringPair;833834  before(async function() {835    await usingEthPlaygrounds(async (helper, privateKey) => {836      donor = await privateKey({filename: __filename});837      [alice] = await helper.arrange.createAccounts([20n], donor);838    });839  });840841  itEth('Events emitted for mint()', async ({helper}) => {842    const collection = await helper.nft.mintCollection(alice, {});843    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);844    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');845846    const events: any = [];847    contract.events.allEvents((_: any, event: any) => {848      events.push(event);849    });850851    const {tokenId} = await collection.mintToken(alice);852    if (events.length == 0) await helper.wait.newBlocks(1);853    const event = events[0];854855    expect(event.event).to.be.equal('Transfer');856    expect(event.address).to.be.equal(collectionAddress);857    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');858    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));859    expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());860  });861862  itEth('Events emitted for burn()', async ({helper}) => {863    const collection = await helper.nft.mintCollection(alice, {});864    const token = await collection.mintToken(alice);865866    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);867    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');868869    const events: any = [];870    contract.events.allEvents((_: any, event: any) => {871      events.push(event);872    });873874    await token.burn(alice);875    if (events.length == 0) await helper.wait.newBlocks(1);876    const event = events[0];877878    expect(event.event).to.be.equal('Transfer');879    expect(event.address).to.be.equal(collectionAddress);880    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));881    expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');882    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());883  });884885  itEth('Events emitted for approve()', async ({helper}) => {886    const receiver = helper.eth.createAccount();887888    const collection = await helper.nft.mintCollection(alice, {});889    const token = await collection.mintToken(alice);890891    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);892    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');893894    const events: any = [];895    contract.events.allEvents((_: any, event: any) => {896      events.push(event);897    });898899    await token.approve(alice, {Ethereum: receiver});900    if (events.length == 0) await helper.wait.newBlocks(1);901    const event = events[0];902903    expect(event.event).to.be.equal('Approval');904    expect(event.address).to.be.equal(collectionAddress);905    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));906    expect(event.returnValues.approved).to.be.equal(receiver);907    expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());908  });909910  itEth('Events emitted for transferFrom()', async ({helper}) => {911    const [bob] = await helper.arrange.createAccounts([10n], donor);912    const receiver = helper.eth.createAccount();913914    const collection = await helper.nft.mintCollection(alice, {});915    const token = await collection.mintToken(alice);916    await token.approve(alice, {Substrate: bob.address});917918    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);919    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');920921    const events: any = [];922    contract.events.allEvents((_: any, event: any) => {923      events.push(event);924    });925926    await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});927928    if (events.length == 0) await helper.wait.newBlocks(1);929    const event = events[0];930931    expect(event.address).to.be.equal(collectionAddress);932    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));933    expect(event.returnValues.to).to.be.equal(receiver);934    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);935  });936937  itEth('Events emitted for transfer()', async ({helper}) => {938    const receiver = helper.eth.createAccount();939940    const collection = await helper.nft.mintCollection(alice, {});941    const token = await collection.mintToken(alice);942943    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);944    const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');945946    const events: any = [];947    contract.events.allEvents((_: any, event: any) => {948      events.push(event);949    });950951    await token.transfer(alice, {Ethereum: receiver});952953    if (events.length == 0) await helper.wait.newBlocks(1);954    const event = events[0];955956    expect(event.address).to.be.equal(collectionAddress);957    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));958    expect(event.returnValues.to).to.be.equal(receiver);959    expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);960  });961});962963describe('Common metadata', () => {964  let donor: IKeyringPair;965  let alice: IKeyringPair;966967  before(async function() {968    await usingEthPlaygrounds(async (helper, privateKey) => {969      donor = await privateKey({filename: __filename});970      [alice] = await helper.arrange.createAccounts([20n], donor);971    });972  });973974  itEth('Returns collection name', async ({helper}) => {975    const caller = await helper.eth.createAccountWithBalance(donor);976    const tokenPropertyPermissions = [{977      key: 'URI',978      permission: {979        mutable: true,980        collectionAdmin: true,981        tokenOwner: false,982      },983    }];984    const collection = await helper.nft.mintCollection(985      alice,986      {987        name: 'oh River',988        tokenPrefix: 'CHANGE',989        properties: [{key: 'ERC721Metadata', value: '1'}],990        tokenPropertyPermissions,991      },992    );993994    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);995    const name = await contract.methods.name().call();996    expect(name).to.equal('oh River');997  });998999  itEth('Returns symbol name', async ({helper}) => {1000    const caller = await helper.eth.createAccountWithBalance(donor);1001    const tokenPropertyPermissions = [{1002      key: 'URI',1003      permission: {1004        mutable: true,1005        collectionAdmin: true,1006        tokenOwner: false,1007      },1008    }];1009    const collection = await helper.nft.mintCollection(1010      alice,1011      {1012        name: 'oh River',1013        tokenPrefix: 'CHANGE',1014        properties: [{key: 'ERC721Metadata', value: '1'}],1015        tokenPropertyPermissions,1016      },1017    );10181019    const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);1020    const symbol = await contract.methods.symbol().call();1021    expect(symbol).to.equal('CHANGE');1022  });1023});10241025describe('Negative tests', () => {1026  let donor: IKeyringPair;1027  let minter: IKeyringPair;1028  let alice: IKeyringPair;10291030  before(async function() {1031    await usingEthPlaygrounds(async (helper, privateKey) => {1032      donor = await privateKey({filename: __filename});1033      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);1034    });1035  });10361037  itEth('[negative] Cant perform burn without approval', async ({helper}) => {1038    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});10391040    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1041    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10421043    const token = await collection.mintToken(minter, {Ethereum: owner});10441045    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1046    const contract = await helper.ethNativeContract.collection(address, 'nft');10471048    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1049    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;10501051    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1052    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10531054    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;1055  });10561057  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {1058    const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});1059    const receiver = alice;10601061    const owner = await helper.eth.createAccountWithBalance(donor, 100n);1062    const spender = await helper.eth.createAccountWithBalance(donor, 100n);10631064    const token = await collection.mintToken(minter, {Ethereum: owner});10651066    const address = helper.ethAddress.fromCollectionId(collection.collectionId);1067    const contract = await helper.ethNativeContract.collection(address, 'nft');10681069    const ownerCross = helper.ethCrossAccount.fromAddress(owner);1070    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);10711072    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;10731074    await contract.methods.setApprovalForAll(spender, true).send({from: owner});1075    await contract.methods.setApprovalForAll(spender, false).send({from: owner});10761077    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1078  });1079});