1234567891011121314151617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';202122describe('NFT: Information getting', () => {23 let donor: IKeyringPair;24 let alice: IKeyringPair;2526 before(async function() {27 await usingEthPlaygrounds(async (helper, privateKey) => {28 donor = await privateKey({filename: __filename});29 [alice] = await helper.arrange.createAccounts([10n], donor);30 });31 });3233 itEth('totalSupply', async ({helper}) => {34 const collection = await helper.nft.mintCollection(alice, {});35 await collection.mintToken(alice);3637 const caller = await helper.eth.createAccountWithBalance(donor);3839 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);40 const totalSupply = await contract.methods.totalSupply().call();4142 expect(totalSupply).to.equal('1');43 });4445 itEth('balanceOf', async ({helper}) => {46 const collection = await helper.nft.mintCollection(alice, {});47 const caller = await helper.eth.createAccountWithBalance(donor);4849 await collection.mintToken(alice, {Ethereum: caller});50 await collection.mintToken(alice, {Ethereum: caller});51 await collection.mintToken(alice, {Ethereum: caller});5253 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);54 const balance = await contract.methods.balanceOf(caller).call();5556 expect(balance).to.equal('3');57 });5859 itEth('ownerOf', async ({helper}) => {60 const collection = await helper.nft.mintCollection(alice, {});61 const caller = await helper.eth.createAccountWithBalance(donor);6263 const token = await collection.mintToken(alice, {Ethereum: caller});6465 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6667 const owner = await contract.methods.ownerOf(token.tokenId).call();6869 expect(owner).to.equal(caller);70 });7172 itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {73 const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});74 const caller = helper.eth.createAccount();7576 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7778 expect(await contract.methods.name().call()).to.equal('test');79 expect(await contract.methods.symbol().call()).to.equal('TEST');80 });81});8283describe('Check ERC721 token URI for NFT', () => {84 let donor: IKeyringPair;8586 before(async function() {87 await usingEthPlaygrounds(async (_helper, privateKey) => {88 donor = await privateKey({filename: __filename});89 });90 });9192 async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = helper.eth.createAccount();9596 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);97 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9899 const result = await contract.methods.mint(receiver).send();100 const tokenId = result.events.Transfer.returnValues.tokenId;101 expect(tokenId).to.be.equal('1');102103 if (propertyKey && propertyValue) {104 105 await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();106 }107108 const event = result.events.Transfer;109 expect(event.address).to.be.equal(collectionAddress);110 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');111 expect(event.returnValues.to).to.be.equal(receiver);112 expect(event.returnValues.tokenId).to.be.equal(tokenId);113114 return {contract, nextTokenId: tokenId};115 }116117 itEth('Empty tokenURI', async ({helper}) => {118 const {contract, nextTokenId} = await setup(helper, '');119 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');120 });121122 itEth('TokenURI from url', async ({helper}) => {123 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');124 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');125 });126127 itEth('TokenURI from baseURI', async ({helper}) => {128 const {contract, nextTokenId} = await setup(helper, 'BaseURI_');129 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');130 });131132 itEth('TokenURI from baseURI + suffix', async ({helper}) => {133 const suffix = '/some/suffix';134 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);135 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);136 });137});138139describe('NFT: Plain calls', () => {140 let donor: IKeyringPair;141 let minter: IKeyringPair;142 let bob: IKeyringPair;143 let charlie: IKeyringPair;144145 before(async function() {146 await usingEthPlaygrounds(async (helper, privateKey) => {147 donor = await privateKey({filename: __filename});148 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);149 });150 });151152 itEth('Can perform mint() & get crossOwner()', async ({helper}) => {153 const owner = await helper.eth.createAccountWithBalance(donor);154 const receiver = helper.eth.createAccount();155156 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');157 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);158159 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();160 const tokenId = result.events.Transfer.returnValues.tokenId;161 expect(tokenId).to.be.equal('1');162163 const event = result.events.Transfer;164 expect(event.address).to.be.equal(collectionAddress);165 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');166 expect(event.returnValues.to).to.be.equal(receiver);167168 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');169 console.log(await contract.methods.crossOwnerOf(tokenId).call());170 expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);171 172 173 174 175 });176177 178 itEth.skip('Can perform mintBulk()', async ({helper}) => {179 const caller = await helper.eth.createAccountWithBalance(donor);180 const receiver = helper.eth.createAccount();181182 const collection = await helper.nft.mintCollection(minter);183 await collection.addAdmin(minter, {Ethereum: caller});184185 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);186 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);187 {188 const bulkSize = 3;189 const nextTokenId = await contract.methods.nextTokenId().call();190 expect(nextTokenId).to.be.equal('1');191 const result = await contract.methods.mintBulkWithTokenURI(192 receiver,193 Array.from({length: bulkSize}, (_, i) => (194 [+nextTokenId + i, `Test URI ${i}`]195 )),196 ).send({from: caller});197198 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);199 for (let i = 0; i < bulkSize; i++) {200 const event = events[i];201 expect(event.address).to.equal(collectionAddress);202 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');203 expect(event.returnValues.to).to.equal(receiver);204 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);205206 expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);207 }208 }209 });210211 itEth('Can perform burn()', async ({helper}) => {212 const caller = await helper.eth.createAccountWithBalance(donor);213214 const collection = await helper.nft.mintCollection(minter, {});215 const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});216217 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);218 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);219220 {221 const result = await contract.methods.burn(tokenId).send({from: caller});222223 const event = result.events.Transfer;224 expect(event.address).to.be.equal(collectionAddress);225 expect(event.returnValues.from).to.be.equal(caller);226 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');227 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);228 }229 });230231 itEth('Can perform approve()', async ({helper}) => {232 const owner = await helper.eth.createAccountWithBalance(donor);233 const spender = helper.eth.createAccount();234235 const collection = await helper.nft.mintCollection(minter, {});236 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});237238 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);239 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);240241 {242 const result = await contract.methods.approve(spender, tokenId).send({from: owner});243244 const event = result.events.Approval;245 expect(event.address).to.be.equal(collectionAddress);246 expect(event.returnValues.owner).to.be.equal(owner);247 expect(event.returnValues.approved).to.be.equal(spender);248 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);249 }250 });251252 itEth('Can perform setApprovalForAll()', async ({helper}) => {253 const owner = await helper.eth.createAccountWithBalance(donor);254 const operator = helper.eth.createAccount();255256 const collection = await helper.nft.mintCollection(minter, {});257258 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);259 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);260261 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();262 expect(approvedBefore).to.be.equal(false);263264 {265 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});266267 expect(result.events.ApprovalForAll).to.be.like({268 address: collectionAddress,269 event: 'ApprovalForAll',270 returnValues: {271 owner,272 operator,273 approved: true,274 },275 });276277 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();278 expect(approvedAfter).to.be.equal(true);279 }280281 {282 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});283284 expect(result.events.ApprovalForAll).to.be.like({285 address: collectionAddress,286 event: 'ApprovalForAll',287 returnValues: {288 owner,289 operator,290 approved: false,291 },292 });293294 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();295 expect(approvedAfter).to.be.equal(false);296 }297 });298299 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {300 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});301302 const owner = await helper.eth.createAccountWithBalance(donor);303 const operator = await helper.eth.createAccountWithBalance(donor, 100n);304305 const token = await collection.mintToken(minter, {Ethereum: owner});306307 const address = helper.ethAddress.fromCollectionId(collection.collectionId);308 const contract = helper.ethNativeContract.collection(address, 'nft');309310 {311 await contract.methods.setApprovalForAll(operator, true).send({from: owner});312 const ownerCross = helper.ethCrossAccount.fromAddress(owner);313 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});314 const events = result.events.Transfer;315316 expect(events).to.be.like({317 address,318 event: 'Transfer',319 returnValues: {320 from: owner,321 to: '0x0000000000000000000000000000000000000000',322 tokenId: token.tokenId.toString(),323 },324 });325 }326 });327 328 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {329 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});330331 const owner = await helper.eth.createAccountWithBalance(donor);332 const operator = await helper.eth.createAccountWithBalance(donor);333 const receiver = charlie;334335 const token = await collection.mintToken(minter, {Ethereum: owner});336337 const address = helper.ethAddress.fromCollectionId(collection.collectionId);338 const contract = helper.ethNativeContract.collection(address, 'nft');339340 {341 await contract.methods.setApprovalForAll(operator, true).send({from: owner});342 const ownerCross = helper.ethCrossAccount.fromAddress(owner);343 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);344 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});345 const event = result.events.Transfer;346 expect(event).to.be.like({347 address: helper.ethAddress.fromCollectionId(collection.collectionId),348 event: 'Transfer',349 returnValues: {350 from: owner,351 to: helper.address.substrateToEth(receiver.address),352 tokenId: token.tokenId.toString(),353 },354 });355 }356357 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});358 });359360 itEth('Can perform burnFromCross()', async ({helper}) => {361 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});362 const ownerSub = bob;363 const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);364 const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);365 const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);366367 const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);368 const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);369370 const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});371 const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});372373 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);374 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft');375376 377 await token1.approve(ownerSub, {Ethereum: burnerEth});378 await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});379380 381 const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});382 const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});383 const events1 = result1.events.Transfer;384 const events2 = result2.events.Transfer;385386 387 [388 [events1, token1, helper.address.substrateToEth(ownerSub.address)], 389 [events2, token2, ownerEth],390 ].map(burnData => {391 expect(burnData[0]).to.be.like({392 address: collectionAddress,393 event: 'Transfer',394 returnValues: {395 from: burnData[2],396 to: '0x0000000000000000000000000000000000000000',397 tokenId: burnData[1].tokenId.toString(),398 },399 });400 });401402 expect(await token1.doesExist()).to.be.false;403 expect(await token2.doesExist()).to.be.false;404 });405406 itEth('Can perform approveCross()', async ({helper}) => {407 408 const owner = await helper.eth.createAccountWithBalance(donor, 100n);409 const ownerCross = helper.ethCrossAccount.fromAddress(owner);410 const receiverSub = charlie;411 const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);412 const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);413 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);414415 416 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});417 const token1 = await collection.mintToken(minter, {Ethereum: owner});418 const token2 = await collection.mintToken(minter, {Ethereum: owner});419420 const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');421422 423 const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});424 const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});425 const eventSub = resultSub.events.Approval;426 const eventEth = resultEth.events.Approval;427 expect(eventSub).to.be.like({428 address: helper.ethAddress.fromCollectionId(collection.collectionId),429 event: 'Approval',430 returnValues: {431 owner,432 approved: helper.address.substrateToEth(receiverSub.address),433 tokenId: token1.tokenId.toString(),434 },435 });436 expect(eventEth).to.be.like({437 address: helper.ethAddress.fromCollectionId(collection.collectionId),438 event: 'Approval',439 returnValues: {440 owner,441 approved: receiverEth,442 tokenId: token2.tokenId.toString(),443 },444 });445446 447 await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});448 expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});449 450 await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});451 expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});452 });453454 itEth('Can reaffirm approved address', async ({helper}) => {455 const owner = await helper.eth.createAccountWithBalance(donor, 100n);456 const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);457 const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);458 const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);459 const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);460 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});461 const token1 = await collection.mintToken(minter, {Ethereum: owner});462 const token2 = await collection.mintToken(minter, {Ethereum: owner});463 const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');464465 466 await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});467 await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});468469 470 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;471 472 await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});473474 475 await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});476 await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});477478 479 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;480 });481482 itEth('Can perform transferFrom()', async ({helper}) => {483 const owner = await helper.eth.createAccountWithBalance(donor);484 const spender = await helper.eth.createAccountWithBalance(donor);485 const receiver = helper.eth.createAccount();486487 const collection = await helper.nft.mintCollection(minter, {});488 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});489490 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);491 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);492493 await contract.methods.approve(spender, tokenId).send({from: owner});494495 {496 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});497498 const event = result.events.Transfer;499 expect(event.address).to.be.equal(collectionAddress);500 expect(event.returnValues.from).to.be.equal(owner);501 expect(event.returnValues.to).to.be.equal(receiver);502 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);503 }504505 {506 const balance = await contract.methods.balanceOf(receiver).call();507 expect(+balance).to.equal(1);508 }509510 {511 const balance = await contract.methods.balanceOf(owner).call();512 expect(+balance).to.equal(0);513 }514 });515516 itEth('Can perform transferFromCross()', async ({helper}) => {517 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});518519 const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);520 const spender = await helper.eth.createAccountWithBalance(donor);521522 const token = await collection.mintToken(minter, {Substrate: owner.address});523524 const address = helper.ethAddress.fromCollectionId(collection.collectionId);525 const contract = helper.ethNativeContract.collection(address, 'nft');526527 await token.approve(owner, {Ethereum: spender});528529 {530 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);531 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);532 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});533 const event = result.events.Transfer;534 expect(event).to.be.like({535 address: helper.ethAddress.fromCollectionId(collection.collectionId),536 event: 'Transfer',537 returnValues: {538 from: helper.address.substrateToEth(owner.address),539 to: helper.address.substrateToEth(receiver.address),540 tokenId: token.tokenId.toString(),541 },542 });543 }544545 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});546 });547548 itEth('Can perform transfer()', async ({helper}) => {549 const collection = await helper.nft.mintCollection(minter, {});550 const owner = await helper.eth.createAccountWithBalance(donor);551 const receiver = helper.eth.createAccount();552553 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});554555 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);556 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);557558 {559 const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});560561 const event = result.events.Transfer;562 expect(event.address).to.be.equal(collectionAddress);563 expect(event.returnValues.from).to.be.equal(owner);564 expect(event.returnValues.to).to.be.equal(receiver);565 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);566 }567568 {569 const balance = await contract.methods.balanceOf(owner).call();570 expect(+balance).to.equal(0);571 }572573 {574 const balance = await contract.methods.balanceOf(receiver).call();575 expect(+balance).to.equal(1);576 }577 });578 579 itEth('Can perform transferCross()', async ({helper}) => {580 const collection = await helper.nft.mintCollection(minter, {});581 const owner = await helper.eth.createAccountWithBalance(donor);582 const receiverEth = await helper.eth.createAccountWithBalance(donor);583 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);584 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);585 586 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});587588 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);589 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);590591 {592 593 const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});594 595 const event = result.events.Transfer;596 expect(event.address).to.be.equal(collectionAddress);597 expect(event.returnValues.from).to.be.equal(owner);598 expect(event.returnValues.to).to.be.equal(receiverEth);599 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);600 601 602 const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();603 expect(+ownerBalance).to.equal(0);604 605 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();606 expect(+receiverBalance).to.equal(1);607 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});608 }609 610 {611 612 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});613 614 const event = substrateResult.events.Transfer;615 expect(event.address).to.be.equal(collectionAddress);616 expect(event.returnValues.from).to.be.equal(receiverEth);617 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));618 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);619 620 621 const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();622 expect(+ownerBalance).to.equal(0);623 624 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});625 expect(receiverBalance).to.contain(tokenId);626 }627 });628629 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {630 const sender = await helper.eth.createAccountWithBalance(donor);631 const tokenOwner = await helper.eth.createAccountWithBalance(donor);632 const receiverSub = minter;633 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);634635 const collection = await helper.nft.mintCollection(minter, {});636 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);637 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', sender);638639 await collection.mintToken(minter, {Ethereum: sender});640 const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});641642 643 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;644 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;645 646 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;647 }));648});649650describe('NFT: Fees', () => {651 let donor: IKeyringPair;652 let alice: IKeyringPair;653 let bob: IKeyringPair;654 let charlie: IKeyringPair;655656 before(async function() {657 await usingEthPlaygrounds(async (helper, privateKey) => {658 donor = await privateKey({filename: __filename});659 [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);660 });661 });662663 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {664 const owner = await helper.eth.createAccountWithBalance(donor);665 const spender = helper.eth.createAccount();666667 const collection = await helper.nft.mintCollection(alice, {});668 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});669670 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);671672 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));673 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));674 });675676 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {677 const owner = await helper.eth.createAccountWithBalance(donor);678 const spender = await helper.eth.createAccountWithBalance(donor);679680 const collection = await helper.nft.mintCollection(alice, {});681 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});682683 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);684685 await contract.methods.approve(spender, tokenId).send({from: owner});686687 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));688 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));689 });690691 itEth('Can perform transferFromCross()', async ({helper}) => {692 const collectionMinter = alice;693 const owner = bob;694 const receiver = charlie;695 const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});696697 const spender = await helper.eth.createAccountWithBalance(donor, 100n);698699 const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});700701 const address = helper.ethAddress.fromCollectionId(collection.collectionId);702 const contract = helper.ethNativeContract.collection(address, 'nft');703704 await token.approve(owner, {Ethereum: spender});705706 {707 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);708 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);709 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});710 const event = result.events.Transfer;711 expect(event).to.be.like({712 address: helper.ethAddress.fromCollectionId(collection.collectionId),713 event: 'Transfer',714 returnValues: {715 from: helper.address.substrateToEth(owner.address),716 to: helper.address.substrateToEth(receiver.address),717 tokenId: token.tokenId.toString(),718 },719 });720 }721722 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});723 });724725 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {726 const owner = await helper.eth.createAccountWithBalance(donor);727 const receiver = helper.eth.createAccount();728729 const collection = await helper.nft.mintCollection(alice, {});730 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});731732 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);733734 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));735 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));736 });737});738739describe('NFT: Substrate calls', () => {740 let donor: IKeyringPair;741 let alice: IKeyringPair;742743 before(async function() {744 await usingEthPlaygrounds(async (helper, privateKey) => {745 donor = await privateKey({filename: __filename});746 [alice] = await helper.arrange.createAccounts([20n], donor);747 });748 });749750 itEth('Events emitted for mint()', async ({helper}) => {751 const collection = await helper.nft.mintCollection(alice, {});752 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);753 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');754755 const events: any = [];756 contract.events.allEvents((_: any, event: any) => {757 events.push(event);758 });759760 const {tokenId} = await collection.mintToken(alice);761 if (events.length == 0) await helper.wait.newBlocks(1);762 const event = events[0];763764 expect(event.event).to.be.equal('Transfer');765 expect(event.address).to.be.equal(collectionAddress);766 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');767 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));768 expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());769 });770771 itEth('Events emitted for burn()', async ({helper}) => {772 const collection = await helper.nft.mintCollection(alice, {});773 const token = await collection.mintToken(alice);774775 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);776 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');777778 const events: any = [];779 contract.events.allEvents((_: any, event: any) => {780 events.push(event);781 });782783 await token.burn(alice);784 if (events.length == 0) await helper.wait.newBlocks(1);785 const event = events[0];786787 expect(event.event).to.be.equal('Transfer');788 expect(event.address).to.be.equal(collectionAddress);789 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));790 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');791 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());792 });793794 itEth('Events emitted for approve()', async ({helper}) => {795 const receiver = helper.eth.createAccount();796797 const collection = await helper.nft.mintCollection(alice, {});798 const token = await collection.mintToken(alice);799800 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);801 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');802803 const events: any = [];804 contract.events.allEvents((_: any, event: any) => {805 events.push(event);806 });807808 await token.approve(alice, {Ethereum: receiver});809 if (events.length == 0) await helper.wait.newBlocks(1);810 const event = events[0];811812 expect(event.event).to.be.equal('Approval');813 expect(event.address).to.be.equal(collectionAddress);814 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));815 expect(event.returnValues.approved).to.be.equal(receiver);816 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());817 });818819 itEth('Events emitted for transferFrom()', async ({helper}) => {820 const [bob] = await helper.arrange.createAccounts([10n], donor);821 const receiver = helper.eth.createAccount();822823 const collection = await helper.nft.mintCollection(alice, {});824 const token = await collection.mintToken(alice);825 await token.approve(alice, {Substrate: bob.address});826827 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);828 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');829830 const events: any = [];831 contract.events.allEvents((_: any, event: any) => {832 events.push(event);833 });834835 await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});836837 if (events.length == 0) await helper.wait.newBlocks(1);838 const event = events[0];839840 expect(event.address).to.be.equal(collectionAddress);841 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));842 expect(event.returnValues.to).to.be.equal(receiver);843 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);844 });845846 itEth('Events emitted for transfer()', async ({helper}) => {847 const receiver = helper.eth.createAccount();848849 const collection = await helper.nft.mintCollection(alice, {});850 const token = await collection.mintToken(alice);851852 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);853 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');854855 const events: any = [];856 contract.events.allEvents((_: any, event: any) => {857 events.push(event);858 });859860 await token.transfer(alice, {Ethereum: receiver});861862 if (events.length == 0) await helper.wait.newBlocks(1);863 const event = events[0];864865 expect(event.address).to.be.equal(collectionAddress);866 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));867 expect(event.returnValues.to).to.be.equal(receiver);868 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);869 });870});871872describe('Common metadata', () => {873 let donor: IKeyringPair;874 let alice: IKeyringPair;875876 before(async function() {877 await usingEthPlaygrounds(async (helper, privateKey) => {878 donor = await privateKey({filename: __filename});879 [alice] = await helper.arrange.createAccounts([20n], donor);880 });881 });882883 itEth('Returns collection name', async ({helper}) => {884 const caller = await helper.eth.createAccountWithBalance(donor);885 const tokenPropertyPermissions = [{886 key: 'URI',887 permission: {888 mutable: true,889 collectionAdmin: true,890 tokenOwner: false,891 },892 }];893 const collection = await helper.nft.mintCollection(894 alice,895 {896 name: 'oh River',897 tokenPrefix: 'CHANGE',898 properties: [{key: 'ERC721Metadata', value: '1'}],899 tokenPropertyPermissions,900 },901 );902903 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);904 const name = await contract.methods.name().call();905 expect(name).to.equal('oh River');906 });907908 itEth('Returns symbol name', async ({helper}) => {909 const caller = await helper.eth.createAccountWithBalance(donor);910 const tokenPropertyPermissions = [{911 key: 'URI',912 permission: {913 mutable: true,914 collectionAdmin: true,915 tokenOwner: false,916 },917 }];918 const collection = await helper.nft.mintCollection(919 alice,920 {921 name: 'oh River',922 tokenPrefix: 'CHANGE',923 properties: [{key: 'ERC721Metadata', value: '1'}],924 tokenPropertyPermissions,925 },926 );927928 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);929 const symbol = await contract.methods.symbol().call();930 expect(symbol).to.equal('CHANGE');931 });932});933934describe('Negative tests', () => {935 let donor: IKeyringPair;936 let minter: IKeyringPair;937 let alice: IKeyringPair;938 let bob: IKeyringPair;939940 before(async function() {941 await usingEthPlaygrounds(async (helper, privateKey) => {942 donor = await privateKey({filename: __filename});943 [minter, alice, bob] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);944 });945 });946947 itEth('[negative] Cant perform burn without approval', async ({helper}) => {948 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});949950 const owner = bob;951 const spender = await helper.eth.createAccountWithBalance(donor, 100n);952953 const token = await collection.mintToken(minter, {Substrate: owner.address});954955 const address = helper.ethAddress.fromCollectionId(collection.collectionId);956 const contract = helper.ethNativeContract.collection(address, 'nft');957958 {959 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);960 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;961 }962 });963964 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {965 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});966 const owner = bob;967 const receiver = alice;968969 const spender = await helper.eth.createAccountWithBalance(donor, 100n);970971 const token = await collection.mintToken(minter, {Substrate: owner.address});972973 const address = helper.ethAddress.fromCollectionId(collection.collectionId);974 const contract = helper.ethNativeContract.collection(address, 'nft');975976 {977 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);978 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);979 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;980 }981 });982});