1import type {IKeyringPair} from '@polkadot/types/types';2import {Contract} from 'web3-eth-contract';34import {itEth, usingEthPlaygrounds, expect} from '../util/index.js';5import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js';67const createNestingCollection = async (8 helper: EthUniqueHelper,9 owner: string,10 mergeDeprecated = false,11): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {12 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');1314 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, mergeDeprecated);15 await contract.methods.setCollectionNesting([true, false, []]).send({from: owner});1617 return {collectionId, collectionAddress, contract};18};192021describe('EVM nesting tests group', () => {22 let donor: IKeyringPair;2324 before(async function() {25 await usingEthPlaygrounds(async (_, privateKey) => {26 donor = await privateKey({url: import.meta.url});27 });28 });2930 describe('Integration Test: EVM Nesting', () => {31 itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {32 const owner = await helper.eth.createAccountWithBalance(donor);33 const {collectionId, contract} = await createNestingCollection(helper, owner);3435 36 const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});37 const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;38 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);3940 41 const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});42 const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;43 expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);4445 46 const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});47 const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;4849 await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});50 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);5152 53 await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});54 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);55 });5657 itEth('NFT: collectionNesting()', async ({helper}) => {58 const owner = await helper.eth.createAccountWithBalance(donor);59 const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');60 const unnestedContract = await helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);61 expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);6263 const {contract} = await createNestingCollection(helper, owner);64 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, []]);65 await contract.methods.setCollectionNesting([true, false, [unnestedCollectionAddress]]).send({from: owner});66 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress]]);67 await contract.methods.setCollectionNesting([false, true, [unnestedCollectionAddress]]).send({from: owner});68 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, true, [unnestedCollectionAddress]]);69 await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});70 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);71 });7273 74 itEth('NFT: collectionNestingRestrictedCollectionIds() & collectionNestingPermissions', async ({helper}) => {75 const owner = await helper.eth.createAccountWithBalance(donor);76 const {collectionId: unnestedCollsectionId, collectionAddress: unnsetedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');77 const unnestedContract = await helper.ethNativeContract.collection(unnsetedCollectionAddress, 'nft', owner, true);78 expect(await unnestedContract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([false, []]);7980 const {contract} = await createNestingCollection(helper, owner, true);81 expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, []]);82 await contract.methods['setCollectionNesting(bool,address[])'](true, [unnsetedCollectionAddress]).send({from: owner});83 expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, [unnestedCollsectionId.toString()]]);84 expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', true]]);85 await contract.methods['setCollectionNesting(bool)'](false).send({from: owner});86 expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', false]]);87 });8889 itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {90 const owner = await helper.eth.createAccountWithBalance(donor);9192 const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);93 const {contract: contractB} = await createNestingCollection(helper, owner);94 await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner});9596 97 const mintingtargetNftTokenIdResult = await contractA.methods.mint(owner).send({from: owner});98 const targetNftTokenId = mintingtargetNftTokenIdResult.events.Transfer.returnValues.tokenId;99 const nftTokenAddressA1 = helper.ethAddress.fromTokenId(collectionIdA, targetNftTokenId);100101 102 const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});103 const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;104105 106 const mintingTokenIdBResult = await contractB.methods.mint(owner).send({from: owner});107 const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;108109 110 await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});111 expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);112113 await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});114 expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);115 });116 });117118 describe('Negative Test: EVM Nesting', () => {119 itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {120 const owner = await helper.eth.createAccountWithBalance(donor);121122 const {collectionId, contract} = await createNestingCollection(helper, owner);123 await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});124125 126 const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});127 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;128 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);129130 131 const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});132 const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;133134 135 await expect(contract.methods136 .transfer(targetNftTokenAddress, nftTokenId)137 .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');138 });139140 itEth('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => {141 const owner = await helper.eth.createAccountWithBalance(donor);142 const malignant = await helper.eth.createAccountWithBalance(donor);143144 const {collectionId, contract} = await createNestingCollection(helper, owner);145146 147 const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});148 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;149 const targetTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);150151 152 const mintingTokenIdResult = await contract.methods.mint(malignant).send({from: owner});153 const tokenId = mintingTokenIdResult.events.Transfer.returnValues.tokenId;154155 156 await expect(contract.methods157 .transfer(targetTokenAddress, tokenId)158 .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');159 });160161 itEth('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => {162 const owner = await helper.eth.createAccountWithBalance(donor);163 const malignant = await helper.eth.createAccountWithBalance(donor);164165 const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);166 const {contract: contractB} = await createNestingCollection(helper, owner);167168 await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner});169170 171 const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});172 const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;173 const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);174175 176 const mintingTokenIdBResult = await contractB.methods.mint(malignant).send({from: owner});177 const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;178179 180 await expect(contractB.methods181 .transfer(nftTokenAddressA, nftTokenIdB)182 .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');183 });184185 itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => {186 const owner = await helper.eth.createAccountWithBalance(donor);187188 const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);189 const {contract: contractB} = await createNestingCollection(helper, owner);190191 await contractA.methods.setCollectionNesting([true, false, [contractA.options.address]]).send({from: owner});192193 194 const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});195 const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;196 const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);197198 199 const mintingTokenIdBResult = await contractB.methods.mint(owner).send({from: owner});200 const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;201202203 204 await expect(contractB.methods205 .transfer(nftTokenAddressA, nftTokenIdB)206 .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');207 });208 });209210 describe('Fungible', () => {211 async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') {212 if(mode === 'ft') {213 const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', '');214 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);215 await contract.methods.mint(owner, 100n).send({from: owner});216 return {collectionAddress, contract};217 }218219 220 const collectionAddress = helper.ethAddress.fromCollectionId(0);221 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);222 return {collectionAddress, contract};223 }224225 [226 {mode: 'ft' as const},227 {mode: 'native ft' as const},228 ].map(testCase => {229 itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => {230 const owner = await helper.eth.createAccountWithBalance(donor);231 const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);232 const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);233234 const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});235 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;236 const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);237238 await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});239 expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10');240 });241 });242243 [244 {mode: 'ft' as const},245 {mode: 'native ft' as const},246 ].map(testCase => {247 itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => {248 const owner = await helper.eth.createAccountWithBalance(donor);249 const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);250 const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);251252 const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});253 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;254 const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);255256 await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});257258 await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});259 expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5');260261 await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});262 expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0');263 });264 });265266 [267 {mode: 'ft' as const},268 {mode: 'native ft' as const},269 ].map(testCase => {270 itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => {271 const owner = await helper.eth.createAccountWithBalance(donor);272 const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);273 await targetContract.methods.setCollectionNesting([false, false, []]).send({from: owner});274275 const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);276277 const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});278 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;279 const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);280281 if(testCase.mode === 'ft') {282 await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');283 } else {284 await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected;285 }286 });287 });288 });289});