1import {IKeyringPair} from '@polkadot/types/types';2import {Contract} from 'web3-eth-contract';34import {itEth, EthUniqueHelper, usingEthPlaygrounds, expect} from '../util';56const createNestingCollection = async (7 helper: EthUniqueHelper,8 owner: string,9 mergeDeprecated = false,10): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {11 const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');1213 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, mergeDeprecated);14 await contract.methods.setCollectionNesting([true, false, []]).send({from: owner});1516 return {collectionId, collectionAddress, contract};17};181920describe('EVM nesting tests group', () => {21 let donor: IKeyringPair;2223 before(async function() {24 await usingEthPlaygrounds(async (_, privateKey) => {25 donor = await privateKey({url: import.meta.url});26 });27 });2829 describe('Integration Test: EVM Nesting', () => {30 itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {31 const owner = await helper.eth.createAccountWithBalance(donor);32 const {collectionId, contract} = await createNestingCollection(helper, owner);3334 35 const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});36 const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;37 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);3839 40 const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});41 const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;42 expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);4344 45 const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});46 const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;4748 await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});49 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);5051 52 await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});53 expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);54 });5556 itEth('NFT: collectionNesting()', async ({helper}) => {57 const owner = await helper.eth.createAccountWithBalance(donor);58 const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');59 const unnestedContract = await helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner);60 expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);6162 const {contract} = await createNestingCollection(helper, owner);63 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, []]);64 await contract.methods.setCollectionNesting([true, false, [unnestedCollectionAddress]]).send({from: owner});65 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress]]);66 await contract.methods.setCollectionNesting([false, true, [unnestedCollectionAddress]]).send({from: owner});67 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, true, [unnestedCollectionAddress]]);68 await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});69 expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]);70 });7172 73 itEth('NFT: collectionNestingRestrictedCollectionIds() & collectionNestingPermissions', async ({helper}) => {74 const owner = await helper.eth.createAccountWithBalance(donor);75 const {collectionId: unnestedCollsectionId, collectionAddress: unnsetedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');76 const unnestedContract = await helper.ethNativeContract.collection(unnsetedCollectionAddress, 'nft', owner, true);77 expect(await unnestedContract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([false, []]);7879 const {contract} = await createNestingCollection(helper, owner, true);80 expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, []]);81 await contract.methods['setCollectionNesting(bool,address[])'](true, [unnsetedCollectionAddress]).send({from: owner});82 expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, [unnestedCollsectionId.toString()]]);83 expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', true]]);84 await contract.methods['setCollectionNesting(bool)'](false).send({from: owner});85 expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', false]]);86 });8788 itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {89 const owner = await helper.eth.createAccountWithBalance(donor);9091 const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);92 const {contract: contractB} = await createNestingCollection(helper, owner);93 await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner});9495 96 const mintingtargetNftTokenIdResult = await contractA.methods.mint(owner).send({from: owner});97 const targetNftTokenId = mintingtargetNftTokenIdResult.events.Transfer.returnValues.tokenId;98 const nftTokenAddressA1 = helper.ethAddress.fromTokenId(collectionIdA, targetNftTokenId);99100 101 const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});102 const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;103104 105 const mintingTokenIdBResult = await contractB.methods.mint(owner).send({from: owner});106 const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;107108 109 await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});110 expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);111112 await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});113 expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);114 });115 });116117 describe('Negative Test: EVM Nesting', () => {118 itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {119 const owner = await helper.eth.createAccountWithBalance(donor);120121 const {collectionId, contract} = await createNestingCollection(helper, owner);122 await contract.methods.setCollectionNesting([false, false, []]).send({from: owner});123124 125 const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});126 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;127 const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);128129 130 const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});131 const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;132133 134 await expect(contract.methods135 .transfer(targetNftTokenAddress, nftTokenId)136 .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');137 });138139 itEth('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => {140 const owner = await helper.eth.createAccountWithBalance(donor);141 const malignant = await helper.eth.createAccountWithBalance(donor);142143 const {collectionId, contract} = await createNestingCollection(helper, owner);144145 146 const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});147 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;148 const targetTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);149150 151 const mintingTokenIdResult = await contract.methods.mint(malignant).send({from: owner});152 const tokenId = mintingTokenIdResult.events.Transfer.returnValues.tokenId;153154 155 await expect(contract.methods156 .transfer(targetTokenAddress, tokenId)157 .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');158 });159160 itEth('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => {161 const owner = await helper.eth.createAccountWithBalance(donor);162 const malignant = await helper.eth.createAccountWithBalance(donor);163164 const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);165 const {contract: contractB} = await createNestingCollection(helper, owner);166167 await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner});168169 170 const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});171 const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;172 const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);173174 175 const mintingTokenIdBResult = await contractB.methods.mint(malignant).send({from: owner});176 const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;177178 179 await expect(contractB.methods180 .transfer(nftTokenAddressA, nftTokenIdB)181 .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');182 });183184 itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => {185 const owner = await helper.eth.createAccountWithBalance(donor);186187 const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);188 const {contract: contractB} = await createNestingCollection(helper, owner);189190 await contractA.methods.setCollectionNesting([true, false, [contractA.options.address]]).send({from: owner});191192 193 const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});194 const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;195 const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);196197 198 const mintingTokenIdBResult = await contractB.methods.mint(owner).send({from: owner});199 const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;200201202 203 await expect(contractB.methods204 .transfer(nftTokenAddressA, nftTokenIdB)205 .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');206 });207 });208209 describe('Fungible', () => {210 async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') {211 if(mode === 'ft') {212 const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', '');213 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);214 await contract.methods.mint(owner, 100n).send({from: owner});215 return {collectionAddress, contract};216 }217218 219 const collectionAddress = helper.ethAddress.fromCollectionId(0);220 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);221 return {collectionAddress, contract};222 }223224 [225 {mode: 'ft' as const},226 {mode: 'native ft' as const},227 ].map(testCase => {228 itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => {229 const owner = await helper.eth.createAccountWithBalance(donor);230 const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);231 const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);232233 const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});234 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;235 const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);236237 await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});238 expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10');239 });240 });241242 [243 {mode: 'ft' as const},244 {mode: 'native ft' as const},245 ].map(testCase => {246 itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => {247 const owner = await helper.eth.createAccountWithBalance(donor);248 const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);249 const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);250251 const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});252 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;253 const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);254255 await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});256257 await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});258 expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5');259260 await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});261 expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0');262 });263 });264265 [266 {mode: 'ft' as const},267 {mode: 'native ft' as const},268 ].map(testCase => {269 itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => {270 const owner = await helper.eth.createAccountWithBalance(donor);271 const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);272 await targetContract.methods.setCollectionNesting([false, false, []]).send({from: owner});273274 const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);275276 const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});277 const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;278 const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);279280 if(testCase.mode === 'ft') {281 await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');282 } else {283 await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected;284 }285 });286 });287 });288});