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