1234567891011121314151617import {itEth, usingEthPlaygrounds, expect} from './util/index.js';18import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';19import type {IKeyringPair} from '@polkadot/types/types';20import {Contract} from 'web3-eth-contract';21import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js';22import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types.js';2324describe('Check ERC721 token URI for NFT', () => {25 let donor: IKeyringPair;2627 before(async function() {28 await usingEthPlaygrounds(async (_helper, privateKey) => {29 donor = await privateKey({url: import.meta.url});30 });31 });3233 async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {34 const owner = await helper.eth.createAccountWithBalance(donor);35 const receiver = helper.eth.createAccount();3637 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);38 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);3940 const result = await contract.methods.mint(receiver).send();41 const tokenId = result.events.Transfer.returnValues.tokenId;42 expect(tokenId).to.be.equal('1');4344 if(propertyKey && propertyValue) {45 46 await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();47 }4849 const event = result.events.Transfer;50 expect(event.address).to.be.equal(collectionAddress);51 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');52 expect(event.returnValues.to).to.be.equal(receiver);53 expect(event.returnValues.tokenId).to.be.equal(tokenId);5455 return {contract, nextTokenId: tokenId};56 }5758 itEth('Empty tokenURI', async ({helper}) => {59 const {contract, nextTokenId} = await setup(helper, '');60 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');61 });6263 itEth('TokenURI from url', async ({helper}) => {64 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');65 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');66 });6768 itEth('TokenURI from baseURI', async ({helper}) => {69 const {contract, nextTokenId} = await setup(helper, 'BaseURI_');70 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');71 });7273 itEth('TokenURI from baseURI + suffix', async ({helper}) => {74 const suffix = '/some/suffix';75 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);76 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);77 });78});7980describe('NFT: Plain calls', () => {81 let donor: IKeyringPair;82 let minter: IKeyringPair;83 let bob: IKeyringPair;84 let charlie: IKeyringPair;8586 before(async function() {87 await usingEthPlaygrounds(async (helper, privateKey) => {88 donor = await privateKey({url: import.meta.url});89 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);90 });91 });9293 94 [95 'substrate' as const,96 'ethereum' as const,97 ].map(testCase => {98 itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {99 const collectionAdmin = await helper.eth.createAccountWithBalance(donor);100101 const receiverEth = helper.eth.createAccount();102 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);103 const receiverSub = bob;104 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);105106 107 const properties = Array(5).fill(0).map((_, i) => ({key: `key_${i}`, value: Buffer.from(`value_${i}`)}));108 const permissions: ITokenPropertyPermission[] = properties109 .map(p => ({110 key: p.key, permission: {111 tokenOwner: false,112 collectionAdmin: true,113 mutable: false,114 },115 }));116117 const collection = await helper.nft.mintCollection(minter, {118 tokenPrefix: 'ethp',119 tokenPropertyPermissions: permissions,120 });121 await collection.addAdmin(minter, {Ethereum: collectionAdmin});122123 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);124 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', collectionAdmin, true);125 let expectedTokenId = await contract.methods.nextTokenId().call();126 let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();127 let tokenId = result.events.Transfer.returnValues.tokenId;128 expect(tokenId).to.be.equal(expectedTokenId);129130 let event = result.events.Transfer;131 expect(event.address).to.be.equal(collectionAddress);132 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');133 expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));134 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);135136 expectedTokenId = await contract.methods.nextTokenId().call();137 result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();138 event = result.events.Transfer;139 expect(event.address).to.be.equal(collectionAddress);140 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');141 expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));142 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);143144 tokenId = result.events.Transfer.returnValues.tokenId;145146 expect(tokenId).to.be.equal(expectedTokenId);147148 expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties149 .map(p => helper.ethProperty.property(p.key, p.value.toString())));150151 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))152 .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});153 });154 });155156 itEth('Non-owner and non admin cannot mintCross', async ({helper}) => {157 const nonOwner = await helper.eth.createAccountWithBalance(donor);158 const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);159160 const collection = await helper.nft.mintCollection(minter);161 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);162 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');163164 await expect(collectionEvm.methods.mintCross(nonOwnerCross, []).call({from: nonOwner}))165 .to.be.rejectedWith('PublicMintingNotAllowed');166 });167168 169 itEth.skip('Can perform mintBulk()', async ({helper}) => {170 const caller = await helper.eth.createAccountWithBalance(donor);171 const receiver = helper.eth.createAccount();172173 const collection = await helper.nft.mintCollection(minter);174 await collection.addAdmin(minter, {Ethereum: caller});175176 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);177 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);178 {179 const bulkSize = 3;180 const nextTokenId = await contract.methods.nextTokenId().call();181 expect(nextTokenId).to.be.equal('1');182 const result = await contract.methods.mintBulkWithTokenURI(183 receiver,184 Array.from({length: bulkSize}, (_, i) => (185 [+nextTokenId + i, `Test URI ${i}`]186 )),187 ).send({from: caller});188189 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);190 for(let i = 0; i < bulkSize; i++) {191 const event = events[i];192 expect(event.address).to.equal(collectionAddress);193 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');194 expect(event.returnValues.to).to.equal(receiver);195 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);196197 expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);198 }199 }200 });201202 itEth('Can perform mintBulkCross()', async ({helper}) => {203 const caller = await helper.eth.createAccountWithBalance(donor);204 const callerCross = helper.ethCrossAccount.fromAddress(caller);205 const receiver = helper.eth.createAccount();206 const receiverCross = helper.ethCrossAccount.fromAddress(receiver);207208 const permissions = [209 {code: TokenPermissionField.Mutable, value: true},210 {code: TokenPermissionField.TokenOwner, value: true},211 {code: TokenPermissionField.CollectionAdmin, value: true},212 ];213 const {collectionAddress} = await helper.eth.createCollection(214 caller,215 {216 ...CREATE_COLLECTION_DATA_DEFAULTS,217 name: 'A',218 description: 'B',219 tokenPrefix: 'C',220 collectionMode: 'nft',221 adminList: [callerCross],222 tokenPropertyPermissions: [223 {key: 'key_0_0', permissions},224 {key: 'key_1_0', permissions},225 {key: 'key_1_1', permissions},226 {key: 'key_2_0', permissions},227 {key: 'key_2_1', permissions},228 {key: 'key_2_2', permissions},229 ],230 },231 ).send();232233 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);234 {235 const nextTokenId = await contract.methods.nextTokenId().call();236 expect(nextTokenId).to.be.equal('1');237 const result = await contract.methods.mintBulkCross([238 {239 owner: receiverCross,240 properties: [241 {key: 'key_0_0', value: Buffer.from('value_0_0')},242 ],243 },244 {245 owner: receiverCross,246 properties: [247 {key: 'key_1_0', value: Buffer.from('value_1_0')},248 {key: 'key_1_1', value: Buffer.from('value_1_1')},249 ],250 },251 {252 owner: receiverCross,253 properties: [254 {key: 'key_2_0', value: Buffer.from('value_2_0')},255 {key: 'key_2_1', value: Buffer.from('value_2_1')},256 {key: 'key_2_2', value: Buffer.from('value_2_2')},257 ],258 },259 ]).send({from: caller});260 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);261 const bulkSize = 3;262 for(let i = 0; i < bulkSize; i++) {263 const event = events[i];264 expect(event.address).to.equal(collectionAddress);265 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');266 expect(event.returnValues.to).to.equal(receiver);267 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);268 }269270 const properties = [271 await contract.methods.properties(+nextTokenId, []).call(),272 await contract.methods.properties(+nextTokenId + 1, []).call(),273 await contract.methods.properties(+nextTokenId + 2, []).call(),274 ];275 expect(properties).to.be.deep.equal([276 [277 ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')],278 ],279 [280 ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')],281 ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')],282 ],283 [284 ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],285 ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],286 ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],287 ],288 ]);289 }290 });291292 itEth('Can perform burn()', async ({helper}) => {293 const caller = await helper.eth.createAccountWithBalance(donor);294295 const collection = await helper.nft.mintCollection(minter, {});296 const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});297298 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);299 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);300301 {302 const result = await contract.methods.burn(tokenId).send({from: caller});303304 const event = result.events.Transfer;305 expect(event.address).to.be.equal(collectionAddress);306 expect(event.returnValues.from).to.be.equal(caller);307 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');308 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);309 }310 });311312 itEth('Can perform approve()', async ({helper}) => {313 const owner = await helper.eth.createAccountWithBalance(donor);314 const spender = helper.eth.createAccount();315316 const collection = await helper.nft.mintCollection(minter, {});317 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});318319 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);320 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);321322 {323 const badTokenId = await contract.methods.nextTokenId().call() + 1;324 await expect(contract.methods.getApproved(badTokenId).call()).to.be.rejectedWith('revert TokenNotFound');325 }326 {327 const approved = await contract.methods.getApproved(tokenId).call();328 expect(approved).to.be.equal('0x0000000000000000000000000000000000000000');329 }330 {331 const result = await contract.methods.approve(spender, tokenId).send({from: owner});332333 const event = result.events.Approval;334 expect(event.address).to.be.equal(collectionAddress);335 expect(event.returnValues.owner).to.be.equal(owner);336 expect(event.returnValues.approved).to.be.equal(spender);337 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);338 }339 {340 const approved = await contract.methods.getApproved(tokenId).call();341 expect(approved).to.be.equal(spender);342 }343 });344345 itEth('Can perform setApprovalForAll()', async ({helper}) => {346 const owner = await helper.eth.createAccountWithBalance(donor);347 const operator = helper.eth.createAccount();348349 const collection = await helper.nft.mintCollection(minter, {});350351 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);352 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);353354 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();355 expect(approvedBefore).to.be.equal(false);356357 {358 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});359360 expect(result.events.ApprovalForAll).to.be.like({361 address: collectionAddress,362 event: 'ApprovalForAll',363 returnValues: {364 owner,365 operator,366 approved: true,367 },368 });369370 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();371 expect(approvedAfter).to.be.equal(true);372 }373374 {375 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});376377 expect(result.events.ApprovalForAll).to.be.like({378 address: collectionAddress,379 event: 'ApprovalForAll',380 returnValues: {381 owner,382 operator,383 approved: false,384 },385 });386387 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();388 expect(approvedAfter).to.be.equal(false);389 }390 });391392 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {393 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});394395 const owner = await helper.eth.createAccountWithBalance(donor);396 const operator = await helper.eth.createAccountWithBalance(donor);397398 const token = await collection.mintToken(minter, {Ethereum: owner});399400 const address = helper.ethAddress.fromCollectionId(collection.collectionId);401 const contract = await helper.ethNativeContract.collection(address, 'nft');402403 {404 await contract.methods.setApprovalForAll(operator, true).send({from: owner});405 const ownerCross = helper.ethCrossAccount.fromAddress(owner);406 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});407 const events = result.events.Transfer;408409 expect(events).to.be.like({410 address,411 event: 'Transfer',412 returnValues: {413 from: owner,414 to: '0x0000000000000000000000000000000000000000',415 tokenId: token.tokenId.toString(),416 },417 });418 }419420 expect(await helper.nft.doesTokenExist(collection.collectionId, token.tokenId)).to.be.false;421 });422423 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {424 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});425426 const owner = await helper.eth.createAccountWithBalance(donor);427 const operator = await helper.eth.createAccountWithBalance(donor);428 const receiver = charlie;429430 const token = await collection.mintToken(minter, {Ethereum: owner});431432 const address = helper.ethAddress.fromCollectionId(collection.collectionId);433 const contract = await helper.ethNativeContract.collection(address, 'nft');434435 {436 await contract.methods.setApprovalForAll(operator, true).send({from: owner});437 const ownerCross = helper.ethCrossAccount.fromAddress(owner);438 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);439 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});440 const event = result.events.Transfer;441 expect(event).to.be.like({442 address: helper.ethAddress.fromCollectionId(collection.collectionId),443 event: 'Transfer',444 returnValues: {445 from: owner,446 to: helper.address.substrateToEth(receiver.address),447 tokenId: token.tokenId.toString(),448 },449 });450 }451452 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});453 });454455 itEth('Can perform burnFromCross()', async ({helper}) => {456 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});457 const ownerSub = bob;458 const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);459 const ownerEth = await helper.eth.createAccountWithBalance(donor);460 const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);461462 const burnerEth = await helper.eth.createAccountWithBalance(donor);463 const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);464465 const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});466 const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});467468 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);469 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft');470471 472 await token1.approve(ownerSub, {Ethereum: burnerEth});473 await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});474475 476 const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});477 const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});478 const events1 = result1.events.Transfer;479 const events2 = result2.events.Transfer;480481 482 [483 [events1, token1, helper.address.substrateToEth(ownerSub.address)],484 [events2, token2, ownerEth],485 ].map(burnData => {486 expect(burnData[0]).to.be.like({487 address: collectionAddress,488 event: 'Transfer',489 returnValues: {490 from: burnData[2],491 to: '0x0000000000000000000000000000000000000000',492 tokenId: burnData[1].tokenId.toString(),493 },494 });495 });496497 expect(await token1.doesExist()).to.be.false;498 expect(await token2.doesExist()).to.be.false;499 });500501 502 itEth('Can perform approveCross()', async ({helper}) => {503 504 const owner = await helper.eth.createAccountWithBalance(donor);505 const ownerCross = helper.ethCrossAccount.fromAddress(owner);506 const receiverSub = charlie;507 const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);508 const receiverEth = await helper.eth.createAccountWithBalance(donor);509 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);510511 512 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});513 const token1 = await collection.mintToken(minter, {Ethereum: owner});514 const token2 = await collection.mintToken(minter, {Ethereum: owner});515516 const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');517518 519 const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});520 const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});521 const eventSub = resultSub.events.Approval;522 const eventEth = resultEth.events.Approval;523 expect(eventSub).to.be.like({524 address: helper.ethAddress.fromCollectionId(collection.collectionId),525 event: 'Approval',526 returnValues: {527 owner,528 approved: helper.address.substrateToEth(receiverSub.address),529 tokenId: token1.tokenId.toString(),530 },531 });532 expect(eventEth).to.be.like({533 address: helper.ethAddress.fromCollectionId(collection.collectionId),534 event: 'Approval',535 returnValues: {536 owner,537 approved: receiverEth,538 tokenId: token2.tokenId.toString(),539 },540 });541542 543 await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});544 expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});545 546 await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});547 expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});548 });549550 itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {551 const nonOwner = await helper.eth.createAccountWithBalance(donor);552 const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);553 const owner = await helper.eth.createAccountWithBalance(donor);554 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});555 const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');556 const token = await collection.mintToken(minter, {Ethereum: owner});557558 await expect(collectionEvm.methods.approveCross(nonOwnerCross, token.tokenId).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');559 });560561 itEth('Can reaffirm approved address', async ({helper}) => {562 const owner = await helper.eth.createAccountWithBalance(donor);563 const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);564 const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);565 const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);566 const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);567 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});568 const token1 = await collection.mintToken(minter, {Ethereum: owner});569 const token2 = await collection.mintToken(minter, {Ethereum: owner});570 const collectionEvm = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');571572 573 await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});574 await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});575576 577 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;578 579 await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});580581 582 await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});583 await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});584585 586 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;587 });588589 itEth('Can perform transferFrom()', async ({helper}) => {590 const owner = await helper.eth.createAccountWithBalance(donor);591 const spender = await helper.eth.createAccountWithBalance(donor);592 const receiver = helper.eth.createAccount();593594 const collection = await helper.nft.mintCollection(minter, {});595 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});596597 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);598 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);599600 await contract.methods.approve(spender, tokenId).send({from: owner});601602 {603 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});604605 const event = result.events.Transfer;606 expect(event.address).to.be.equal(collectionAddress);607 expect(event.returnValues.from).to.be.equal(owner);608 expect(event.returnValues.to).to.be.equal(receiver);609 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);610 }611612 {613 const balance = await contract.methods.balanceOf(receiver).call();614 expect(+balance).to.equal(1);615 }616617 {618 const balance = await contract.methods.balanceOf(owner).call();619 expect(+balance).to.equal(0);620 }621 });622623 itEth('Can perform transferFromCross()', async ({helper}) => {624 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});625626 const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);627 const spender = await helper.eth.createAccountWithBalance(donor);628629 const token = await collection.mintToken(minter, {Substrate: owner.address});630631 const address = helper.ethAddress.fromCollectionId(collection.collectionId);632 const contract = await helper.ethNativeContract.collection(address, 'nft');633634 await token.approve(owner, {Ethereum: spender});635636 {637 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);638 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);639 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});640 const event = result.events.Transfer;641 expect(event).to.be.like({642 address: helper.ethAddress.fromCollectionId(collection.collectionId),643 event: 'Transfer',644 returnValues: {645 from: helper.address.substrateToEth(owner.address),646 to: helper.address.substrateToEth(receiver.address),647 tokenId: token.tokenId.toString(),648 },649 });650 }651652 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});653 });654655 itEth('Can perform transfer()', async ({helper}) => {656 const collection = await helper.nft.mintCollection(minter, {});657 const owner = await helper.eth.createAccountWithBalance(donor);658 const receiver = helper.eth.createAccount();659660 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});661662 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);663 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);664665 {666 const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});667668 const event = result.events.Transfer;669 expect(event.address).to.be.equal(collectionAddress);670 expect(event.returnValues.from).to.be.equal(owner);671 expect(event.returnValues.to).to.be.equal(receiver);672 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);673 }674675 {676 const balance = await contract.methods.balanceOf(owner).call();677 expect(+balance).to.equal(0);678 }679680 {681 const balance = await contract.methods.balanceOf(receiver).call();682 expect(+balance).to.equal(1);683 }684 });685686 itEth('Can perform transferCross()', async ({helper}) => {687 const collection = await helper.nft.mintCollection(minter, {});688 const owner = await helper.eth.createAccountWithBalance(donor);689 const receiverEth = await helper.eth.createAccountWithBalance(donor);690 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);691 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);692693 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});694695 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);696 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);697698 {699 700 const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});701 702 const event = result.events.Transfer;703 expect(event.address).to.be.equal(collectionAddress);704 expect(event.returnValues.from).to.be.equal(owner);705 expect(event.returnValues.to).to.be.equal(receiverEth);706 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);707708 709 const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();710 expect(+ownerBalance).to.equal(0);711 712 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();713 expect(+receiverBalance).to.equal(1);714 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});715 }716717 {718 719 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});720 721 const event = substrateResult.events.Transfer;722 expect(event.address).to.be.equal(collectionAddress);723 expect(event.returnValues.from).to.be.equal(receiverEth);724 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));725 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);726727 728 const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();729 expect(+ownerBalance).to.equal(0);730 731 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});732 expect(receiverBalance).to.contain(tokenId);733 }734 });735736 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {737 const sender = await helper.eth.createAccountWithBalance(donor);738 const tokenOwner = await helper.eth.createAccountWithBalance(donor);739 const receiverSub = minter;740 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);741742 const collection = await helper.nft.mintCollection(minter, {});743 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);744 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', sender);745746 await collection.mintToken(minter, {Ethereum: sender});747 const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});748749 750 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;751 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;752 753 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;754 }));755756 itEth('Check balanceOfCross()', async ({helper}) => {757 const collection = await helper.nft.mintCollection(minter, {});758 const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);759 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);760 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner.eth);761762 expect(await collectionEvm.methods.balanceOfCross(owner).call({from: owner.eth})).to.be.eq('0');763764 for(let i = 1; i < 10; i++) {765 await collection.mintToken(minter, {Ethereum: owner.eth});766 expect(await collectionEvm.methods.balanceOfCross(owner).call({from: owner.eth})).to.be.eq(i.toString());767 }768 });769770 itEth('Check ownerOfCross()', async ({helper}) => {771 const collection = await helper.nft.mintCollection(minter, {});772 let owner = await helper.ethCrossAccount.createAccountWithBalance(donor);773 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);774 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner.eth);775 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner.eth});776777 for(let i = 1n; i < 10n; i++) {778 const ownerCross = await collectionEvm.methods.ownerOfCross(tokenId).call({from: owner.eth});779 expect(ownerCross.eth).to.be.eq(owner.eth);780 expect(ownerCross.sub).to.be.eq(owner.sub);781782 const newOwner = await helper.ethCrossAccount.createAccountWithBalance(donor);783 await collectionEvm.methods.transferCross(newOwner, tokenId).send({from: owner.eth});784 owner = newOwner;785 }786 });787});788789describe('NFT: Fees', () => {790 let donor: IKeyringPair;791 let alice: IKeyringPair;792 let bob: IKeyringPair;793 let charlie: IKeyringPair;794795 before(async function() {796 await usingEthPlaygrounds(async (helper, privateKey) => {797 donor = await privateKey({url: import.meta.url});798 [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);799 });800 });801802 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {803 const owner = await helper.eth.createAccountWithBalance(donor);804 const spender = helper.eth.createAccount();805806 const collection = await helper.nft.mintCollection(alice, {});807 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});808809 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);810811 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));812 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));813 });814815 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {816 const owner = await helper.eth.createAccountWithBalance(donor);817 const spender = await helper.eth.createAccountWithBalance(donor);818819 const collection = await helper.nft.mintCollection(alice, {});820 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});821822 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);823824 await contract.methods.approve(spender, tokenId).send({from: owner});825826 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));827 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));828 });829830 itEth('Can perform transferFromCross()', async ({helper}) => {831 const collectionMinter = alice;832 const owner = bob;833 const receiver = charlie;834 const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});835836 const spender = await helper.eth.createAccountWithBalance(donor);837838 const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});839840 const address = helper.ethAddress.fromCollectionId(collection.collectionId);841 const contract = await helper.ethNativeContract.collection(address, 'nft');842843 await token.approve(owner, {Ethereum: spender});844845 {846 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);847 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);848 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});849 const event = result.events.Transfer;850 expect(event).to.be.like({851 address: helper.ethAddress.fromCollectionId(collection.collectionId),852 event: 'Transfer',853 returnValues: {854 from: helper.address.substrateToEth(owner.address),855 to: helper.address.substrateToEth(receiver.address),856 tokenId: token.tokenId.toString(),857 },858 });859 }860861 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});862 });863864 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {865 const owner = await helper.eth.createAccountWithBalance(donor);866 const receiver = helper.eth.createAccount();867868 const collection = await helper.nft.mintCollection(alice, {});869 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});870871 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);872873 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));874 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));875 });876});877878describe('NFT: Substrate calls', () => {879 let donor: IKeyringPair;880 let alice: IKeyringPair;881882 before(async function() {883 await usingEthPlaygrounds(async (helper, privateKey) => {884 donor = await privateKey({url: import.meta.url});885 [alice] = await helper.arrange.createAccounts([20n], donor);886 });887 });888889 itEth('Events emitted for mint()', async ({helper}) => {890 const collection = await helper.nft.mintCollection(alice, {});891 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 const {tokenId} = await collection.mintToken(alice);900 if(events.length == 0) await helper.wait.newBlocks(1);901 const event = events[0];902903 expect(event.event).to.be.equal('Transfer');904 expect(event.address).to.be.equal(collectionAddress);905 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');906 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));907 expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());908 });909910 itEth('Events emitted for burn()', async ({helper}) => {911 const collection = await helper.nft.mintCollection(alice, {});912 const token = await collection.mintToken(alice);913914 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);915 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');916917 const events: any = [];918 contract.events.allEvents((_: any, event: any) => {919 events.push(event);920 });921922 await token.burn(alice);923 if(events.length == 0) await helper.wait.newBlocks(1);924 const event = events[0];925926 expect(event.event).to.be.equal('Transfer');927 expect(event.address).to.be.equal(collectionAddress);928 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));929 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');930 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());931 });932933 itEth('Events emitted for approve()', async ({helper}) => {934 const receiver = helper.eth.createAccount();935936 const collection = await helper.nft.mintCollection(alice, {});937 const token = await collection.mintToken(alice);938939 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);940 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');941942 const events: any = [];943 contract.events.allEvents((_: any, event: any) => {944 events.push(event);945 });946947 await token.approve(alice, {Ethereum: receiver});948 if(events.length == 0) await helper.wait.newBlocks(1);949 const event = events[0];950951 expect(event.event).to.be.equal('Approval');952 expect(event.address).to.be.equal(collectionAddress);953 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));954 expect(event.returnValues.approved).to.be.equal(receiver);955 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());956 });957958 itEth('Events emitted for transferFrom()', async ({helper}) => {959 const [bob] = await helper.arrange.createAccounts([10n], donor);960 const receiver = helper.eth.createAccount();961962 const collection = await helper.nft.mintCollection(alice, {});963 const token = await collection.mintToken(alice);964 await token.approve(alice, {Substrate: bob.address});965966 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);967 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');968969 const events: any = [];970 contract.events.allEvents((_: any, event: any) => {971 events.push(event);972 });973974 await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});975976 if(events.length == 0) await helper.wait.newBlocks(1);977 const event = events[0];978979 expect(event.address).to.be.equal(collectionAddress);980 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));981 expect(event.returnValues.to).to.be.equal(receiver);982 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);983 });984985 itEth('Events emitted for transfer()', async ({helper}) => {986 const receiver = helper.eth.createAccount();987988 const collection = await helper.nft.mintCollection(alice, {});989 const token = await collection.mintToken(alice);990991 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);992 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft');993994 const events: any = [];995 contract.events.allEvents((_: any, event: any) => {996 events.push(event);997 });998999 await token.transfer(alice, {Ethereum: receiver});10001001 if(events.length == 0) await helper.wait.newBlocks(1);1002 const event = events[0];10031004 expect(event.address).to.be.equal(collectionAddress);1005 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));1006 expect(event.returnValues.to).to.be.equal(receiver);1007 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);1008 });1009});10101011describe('Common metadata', () => {1012 let donor: IKeyringPair;1013 let alice: IKeyringPair;10141015 before(async function() {1016 await usingEthPlaygrounds(async (helper, privateKey) => {1017 donor = await privateKey({url: import.meta.url});1018 [alice] = await helper.arrange.createAccounts([20n], donor);1019 });1020 });10211022 itEth('Returns collection name', async ({helper}) => {1023 const caller = helper.eth.createAccount();1024 const tokenPropertyPermissions = [{1025 key: 'URI',1026 permission: {1027 mutable: true,1028 collectionAdmin: true,1029 tokenOwner: false,1030 },1031 }];1032 const collection = await helper.nft.mintCollection(1033 alice,1034 {1035 name: 'oh River',1036 tokenPrefix: 'CHANGE',1037 properties: [{key: 'ERC721Metadata', value: '1'}],1038 tokenPropertyPermissions,1039 },1040 );10411042 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);1043 const name = await contract.methods.name().call();1044 expect(name).to.equal('oh River');1045 });10461047 itEth('Returns symbol name', async ({helper}) => {1048 const caller = await helper.eth.createAccountWithBalance(donor);1049 const tokenPropertyPermissions = [{1050 key: 'URI',1051 permission: {1052 mutable: true,1053 collectionAdmin: true,1054 tokenOwner: false,1055 },1056 }];1057 const collection = await helper.nft.mintCollection(1058 alice,1059 {1060 name: 'oh River',1061 tokenPrefix: 'CHANGE',1062 properties: [{key: 'ERC721Metadata', value: '1'}],1063 tokenPropertyPermissions,1064 },1065 );10661067 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);1068 const symbol = await contract.methods.symbol().call();1069 expect(symbol).to.equal('CHANGE');1070 });1071});10721073describe('Negative tests', () => {1074 let donor: IKeyringPair;1075 let minter: IKeyringPair;1076 let alice: IKeyringPair;10771078 before(async function() {1079 await usingEthPlaygrounds(async (helper, privateKey) => {1080 donor = await privateKey({url: import.meta.url});1081 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);1082 });1083 });10841085 itEth('[negative] Cant perform burn without approval', async ({helper}) => {1086 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});10871088 const owner = await helper.eth.createAccountWithBalance(donor);1089 const spender = await helper.eth.createAccountWithBalance(donor);10901091 const token = await collection.mintToken(minter, {Ethereum: owner});10921093 const address = helper.ethAddress.fromCollectionId(collection.collectionId);1094 const contract = await helper.ethNativeContract.collection(address, 'nft');10951096 const ownerCross = helper.ethCrossAccount.fromAddress(owner);1097 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;10981099 await contract.methods.setApprovalForAll(spender, true).send({from: owner});1100 await contract.methods.setApprovalForAll(spender, false).send({from: owner});11011102 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;1103 });11041105 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {1106 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});1107 const receiver = alice;11081109 const owner = await helper.eth.createAccountWithBalance(donor);1110 const spender = await helper.eth.createAccountWithBalance(donor);11111112 const token = await collection.mintToken(minter, {Ethereum: owner});11131114 const address = helper.ethAddress.fromCollectionId(collection.collectionId);1115 const contract = await helper.ethNativeContract.collection(address, 'nft');11161117 const ownerCross = helper.ethCrossAccount.fromAddress(owner);1118 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);11191120 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;11211122 await contract.methods.setApprovalForAll(spender, true).send({from: owner});1123 await contract.methods.setApprovalForAll(spender, false).send({from: owner});11241125 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1126 });1127});