git.delta.rocks / unique-network / refs/commits / c9f3344fa8b9

difftreelog

test Initial eth playgrounds

Andrey2022-09-04parent: #21a2ff1.patch.diff
in: master

4 files changed

modifiedtests/package.jsondiffbeforeafterboth
before · tests/package.json
1{2  "name": "unique-tests",3  "version": "1.0.0",4  "description": "Unique Chain Tests",5  "main": "",6  "devDependencies": {7    "@polkadot/ts": "0.4.22",8    "@polkadot/typegen": "9.2.2",9    "@types/chai": "^4.3.1",10    "@types/chai-as-promised": "^7.1.5",11    "@types/chai-like": "^1.1.1",12    "@types/mocha": "^9.1.1",13    "@types/node": "^17.0.35",14    "@typescript-eslint/eslint-plugin": "^5.26.0",15    "@typescript-eslint/parser": "^5.26.0",16    "chai": "^4.3.6",17    "eslint": "^8.16.0",18    "mocha": "^10.0.0",19    "ts-node": "^10.8.0",20    "typescript": "^4.7.2"21  },22  "mocha": {23    "timeout": 9999999,24    "require": "ts-node/register"25  },26  "scripts": {27    "lint": "eslint --ext .ts,.js src/",28    "fix": "eslint --ext .ts,.js src/ --fix",29    "test": "mocha --timeout 9999999 -r ts-node/register './src/**/*.test.ts'",30    "testEth": "mocha --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'",31    "testEthMarketplace": "mocha --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'",32    "testEthNesting": "mocha --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'",33    "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",34    "loadTransfer": "ts-node src/transfer.nload.ts",35    "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",36    "testEvent": "mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",37    "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",38    "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",39    "testStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",40    "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts",41    "testMigration": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",42    "testRmrk": "mocha --timeout 9999999 -r ts-node/register ./**/rmrk/**.test.ts",43    "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",44    "testSetSchemaVersion": "mocha --timeout 9999999 -r ts-node/register ./**/setSchemaVersion.test.ts",45    "testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",46    "testSetCollectionSponsor": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionSponsor.test.ts",47    "testConfirmSponsorship": "mocha --timeout 9999999 -r ts-node/register ./**/confirmSponsorship.test.ts",48    "testRemoveCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionAdmin.test.ts",49    "testRemoveCollectionSponsor": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionSponsor.test.ts",50    "testRemoveFromAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromAllowList.test.ts",51    "testConnection": "mocha --timeout 9999999 -r ts-node/register ./**/connection.test.ts",52    "testContracts": "mocha --timeout 9999999 -r ts-node/register ./**/contracts.test.ts",53    "testCreateItem": "mocha --timeout 9999999 -r ts-node/register ./**/createItem.test.ts",54    "testCreateMultipleItems": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItems.test.ts",55    "testCreateMultipleItemsEx": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItemsEx.test.ts",56    "testApprove": "mocha --timeout 9999999 -r ts-node/register ./**/approve.test.ts",57    "testTransferFrom": "mocha --timeout 9999999 -r ts-node/register ./**/transferFrom.test.ts",58    "testCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",59    "testDestroyCollection": "mocha --timeout 9999999 -r ts-node/register ./**/destroyCollection.test.ts",60    "testToggleContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/toggleContractAllowList.test.ts",61    "testAddToContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/addToContractAllowList.test.ts",62    "testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts",63    "testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts",64    "testAdminTransferAndBurn": "mocha --timeout 9999999 -r ts-node/register ./**/adminTransferAndBurn.test.ts",65    "testSetMintPermission": "mocha --timeout 9999999 -r ts-node/register ./**/setMintPermission.test.ts",66    "testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts",67    "testContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/contractSponsoring.test.ts",68    "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts",69    "testRemoveFromContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromContractAllowList.test.ts",70    "testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts",71    "testSetOffchainSchema": "mocha --timeout 9999999 -r ts-node/register ./**/setOffchainSchema.test.ts",72    "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",73    "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",74    "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",75    "testScheduler": "mocha --timeout 9999999 -r ts-node/register ./**/scheduler.test.ts",76    "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts",77    "testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",78    "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",79    "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",80    "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",81    "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",82    "testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts",83    "testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts",84    "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts",85    "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",86    "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",87    "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",88    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",89    "polkadot-types": "echo \"export default {}\" > src/interfaces/lookup.ts && yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"90  },91  "author": "",92  "license": "SEE LICENSE IN ../LICENSE",93  "homepage": "",94  "dependencies": {95    "@polkadot/api": "9.2.2",96    "@polkadot/api-contract": "9.2.2",97    "@polkadot/util-crypto": "10.1.1",98    "bignumber.js": "^9.0.2",99    "chai-as-promised": "^7.1.1",100    "chai-like": "^1.1.1",101    "find-process": "^1.4.7",102    "solc": "0.8.14-fixed",103    "web3": "^1.7.3"104  },105  "standard": {106    "globals": [107      "it",108      "assert",109      "beforeEach",110      "afterEach",111      "describe",112      "contract",113      "artifacts"114    ]115  },116  "resolutions": {117    "simple-get": "^4.0.1"118  }119}
modifiedtests/src/eth/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -1,217 +1,220 @@
-import {ApiPromise} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
-import {expect} from 'chai';
-import Web3 from 'web3';
-import {createEthAccountWithBalance, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, tokenIdToAddress} from '../../eth/util/helpers';
-import nonFungibleAbi from '../nonFungibleAbi.json';
 
+import {itEth, EthUniqueHelper, usingEthPlaygrounds, expect} from '../util/playgrounds'
+
 const createNestingCollection = async (
-  api: ApiPromise,
-  web3: Web3,
+  helper: EthUniqueHelper,
   owner: string,
 ): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {
-  const collectionHelper = evmCollectionHelpers(web3, owner);
-        
-  const result = await collectionHelper.methods
-    .createNonfungibleCollection('A', 'B', 'C')
-    .send();
-  const {collectionIdAddress: collectionAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+  const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C');
 
-  const contract = new web3.eth.Contract(nonFungibleAbi as any, collectionAddress, {from: owner, ...GAS_ARGS});
+  const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
   await contract.methods.setCollectionNesting(true).send({from: owner});
 
   return {collectionId, collectionAddress, contract};
 };
 
-describe('Integration Test: EVM Nesting', () => {
-  itWeb3('NFT: allows an Owner to nest/unnest their token', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const {collectionId, contract} = await createNestingCollection(api, web3, owner);
 
-    // Create a token to be nested
-    const targetNFTTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      targetNFTTokenId,
-    ).send({from: owner});
-
-    const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNFTTokenId);
-
-    // Create a nested token
-    const firstTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      targetNftTokenAddress,
-      firstTokenId,
-    ).send({from: owner});
+describe('EVM nesting tests group', () => {
+  let donor: IKeyringPair;
 
-    expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);
-
-    // Create a token to be nested and nest
-    const secondTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      secondTokenId,
-    ).send({from: owner});
-
-    await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});
-
-    expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);
-
-    // Unnest token back
-    await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});
-    expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = privateKey('//Alice');
+    });
   });
 
-  itWeb3('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner);
-    const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(api, web3, owner);
-    await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
-
-    // Create a token to nest into
-    const targetNftTokenId = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      targetNftTokenId,
-    ).send({from: owner});
-    const nftTokenAddressA1 = tokenIdToAddress(collectionIdA, targetNftTokenId);
-
-    // Create a token for nesting in the same collection as the target
-    const nftTokenIdA = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      nftTokenIdA,
-    ).send({from: owner});
-
-    // Create a token for nesting in a different collection
-    const nftTokenIdB = await contractB.methods.nextTokenId().call();
-    await contractB.methods.mint(
-      owner,
-      nftTokenIdB,
-    ).send({from: owner});
-
-    // Nest
-    await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});
-    expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);
-
-    await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});
-    expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);
-  });
-});
-
-describe('Negative Test: EVM Nesting', async() => {
-  itWeb3('NFT: disallows to nest token if nesting is disabled', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId, contract} = await createNestingCollection(api, web3, owner);
-    await contract.methods.setCollectionNesting(false).send({from: owner});
-
-    // Create a token to nest into
-    const targetNftTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      targetNftTokenId,
-    ).send({from: owner});
-
-    const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNftTokenId);
-
-    // Create a token to nest
-    const nftTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      nftTokenId,
-    ).send({from: owner});
-
-    // Try to nest
-    await expect(contract.methods
-      .transfer(targetNftTokenAddress, nftTokenId)
-      .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
-  });
-
-  itWeb3('NFT: disallows a non-Owner to nest someone else\'s token', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId, contract} = await createNestingCollection(api, web3, owner);
-
-    // Mint a token
-    const targetTokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      owner,
-      targetTokenId,
-    ).send({from: owner});
-    const targetTokenAddress = tokenIdToAddress(collectionId, targetTokenId);
-
-    // Mint a token belonging to a different account
-    const tokenId = await contract.methods.nextTokenId().call();
-    await contract.methods.mint(
-      malignant,
-      tokenId,
-    ).send({from: owner});
-
-    // Try to nest one token in another as a non-owner account
-    await expect(contract.methods
-      .transfer(targetTokenAddress, tokenId)
-      .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
-  });
-
-  itWeb3('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner);
-    const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(api, web3, owner);
-
-    await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
-
-    // Create a token in one collection
-    const nftTokenIdA = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      nftTokenIdA,
-    ).send({from: owner});
-    const nftTokenAddressA = tokenIdToAddress(collectionIdA, nftTokenIdA);
-
-    // Create a token in another collection belonging to someone else
-    const nftTokenIdB = await contractB.methods.nextTokenId().call();
-    await contractB.methods.mint(
-      malignant,
-      nftTokenIdB,
-    ).send({from: owner});
-
-    // Try to drag someone else's token into the other collection and nest
-    await expect(contractB.methods
-      .transfer(nftTokenAddressA, nftTokenIdB)
-      .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
+  describe('Integration Test: EVM Nesting', () => {
+    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const {collectionId, contract} = await createNestingCollection(helper, owner);
+  
+      // Create a token to be nested
+      const targetNFTTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        targetNFTTokenId,
+      ).send({from: owner});
+  
+      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);
+  
+      // Create a nested token
+      const firstTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        targetNftTokenAddress,
+        firstTokenId,
+      ).send({from: owner});
+  
+      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);
+  
+      // Create a token to be nested and nest
+      const secondTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        secondTokenId,
+      ).send({from: owner});
+  
+      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});
+  
+      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);
+  
+      // Unnest token back
+      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});
+      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);
+    });
+  
+    itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);
+      const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner);
+      await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
+  
+      // Create a token to nest into
+      const targetNftTokenId = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        targetNftTokenId,
+      ).send({from: owner});
+      const nftTokenAddressA1 = helper.ethAddress.fromTokenId(collectionIdA, targetNftTokenId);
+  
+      // Create a token for nesting in the same collection as the target
+      const nftTokenIdA = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        nftTokenIdA,
+      ).send({from: owner});
+  
+      // Create a token for nesting in a different collection
+      const nftTokenIdB = await contractB.methods.nextTokenId().call();
+      await contractB.methods.mint(
+        owner,
+        nftTokenIdB,
+      ).send({from: owner});
+  
+      // Nest
+      await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});
+      expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);
+  
+      await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});
+      expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);
+    });
   });
 
-  itWeb3('NFT: disallows to nest token in an unlisted collection', async ({api, web3, privateKeyWrapper}) => {
-    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-
-    const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner);
-    const {contract: contractB} = await createNestingCollection(api, web3, owner);
-
-    await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner});
-
-    // Create a token in one collection
-    const nftTokenIdA = await contractA.methods.nextTokenId().call();
-    await contractA.methods.mint(
-      owner,
-      nftTokenIdA,
-    ).send({from: owner});
-    const nftTokenAddressA = tokenIdToAddress(collectionIdA, nftTokenIdA);
-
-    // Create a token in another collection
-    const nftTokenIdB = await contractB.methods.nextTokenId().call();
-    await contractB.methods.mint(
-      owner,
-      nftTokenIdB,
-    ).send({from: owner});
-
-    // Try to nest into a token in the other collection, disallowed in the first
-    await expect(contractB.methods
-      .transfer(nftTokenAddressA, nftTokenIdB)
-      .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
+  describe('Negative Test: EVM Nesting', async() => {
+    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId, contract} = await createNestingCollection(helper, owner);
+      await contract.methods.setCollectionNesting(false).send({from: owner});
+  
+      // Create a token to nest into
+      const targetNftTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        targetNftTokenId,
+      ).send({from: owner});
+  
+      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNftTokenId);
+  
+      // Create a token to nest
+      const nftTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        nftTokenId,
+      ).send({from: owner});
+  
+      // Try to nest
+      await expect(contract.methods
+        .transfer(targetNftTokenAddress, nftTokenId)
+        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
+    });
+  
+    itEth('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const malignant = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId, contract} = await createNestingCollection(helper, owner);
+  
+      // Mint a token
+      const targetTokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        owner,
+        targetTokenId,
+      ).send({from: owner});
+      const targetTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);
+  
+      // Mint a token belonging to a different account
+      const tokenId = await contract.methods.nextTokenId().call();
+      await contract.methods.mint(
+        malignant,
+        tokenId,
+      ).send({from: owner});
+  
+      // Try to nest one token in another as a non-owner account
+      await expect(contract.methods
+        .transfer(targetTokenAddress, tokenId)
+        .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
+    });
+  
+    itEth('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const malignant = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);
+      const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner);
+  
+      await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});
+  
+      // Create a token in one collection
+      const nftTokenIdA = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        nftTokenIdA,
+      ).send({from: owner});
+      const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);
+  
+      // Create a token in another collection belonging to someone else
+      const nftTokenIdB = await contractB.methods.nextTokenId().call();
+      await contractB.methods.mint(
+        malignant,
+        nftTokenIdB,
+      ).send({from: owner});
+  
+      // Try to drag someone else's token into the other collection and nest
+      await expect(contractB.methods
+        .transfer(nftTokenAddressA, nftTokenIdB)
+        .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');
+    });
+  
+    itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+  
+      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);
+      const {contract: contractB} = await createNestingCollection(helper, owner);
+  
+      await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner});
+  
+      // Create a token in one collection
+      const nftTokenIdA = await contractA.methods.nextTokenId().call();
+      await contractA.methods.mint(
+        owner,
+        nftTokenIdA,
+      ).send({from: owner});
+      const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);
+  
+      // Create a token in another collection
+      const nftTokenIdB = await contractB.methods.nextTokenId().call();
+      await contractB.methods.mint(
+        owner,
+        nftTokenIdB,
+      ).send({from: owner});
+  
+      // Try to nest into a token in the other collection, disallowed in the first
+      await expect(contractB.methods
+        .transfer(nftTokenAddressA, nftTokenIdB)
+        .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
+    });
   });
 });
addedtests/src/eth/util/playgrounds/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/util/playgrounds/index.ts
@@ -0,0 +1,67 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import {IKeyringPair} from '@polkadot/types/types';
+
+import config from '../../../config';
+
+import {EthUniqueHelper} from './unique.dev';
+import {SilentLogger} from '../../../util/playgrounds/unique.dev';
+
+export {EthUniqueHelper} from './unique.dev';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+chai.use(chaiAsPromised);
+export const expect = chai.expect;
+
+export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
+  // TODO: Remove, this is temporary: Filter unneeded API output
+  // (Jaco promised it will be removed in the next version)
+  const consoleErr = console.error;
+  const consoleLog = console.log;
+  const consoleWarn = console.warn;
+
+  const outFn = (printer: any) => (...args: any[]) => {
+    for (const arg of args) {
+      if (typeof arg !== 'string')
+        continue;
+      if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis: UniqueApi/2, RmrkApi/1') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')
+        return;
+    }
+    printer(...args);
+  };
+
+  console.error = outFn(consoleErr.bind(console));
+  console.log = outFn(consoleLog.bind(console));
+  console.warn = outFn(consoleWarn.bind(console));
+  const helper = new EthUniqueHelper(new SilentLogger());
+
+  try {
+    await helper.connect(config.substrateUrl);
+    await helper.connectWeb3(config.substrateUrl);
+    const ss58Format = helper.chain.getChainProperties().ss58Format;
+    const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format);
+    await code(helper, privateKey);
+  }
+  finally {
+    await helper.disconnect();
+    await helper.disconnectWeb3();
+    console.error = consoleErr;
+    console.log = consoleLog;
+    console.warn = consoleWarn;
+  }
+}
+  
+export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) {
+  let i: any = it;
+  if (opts.only) i = i.only;
+  else if (opts.skip) i = i.skip;
+  i(name, async () => {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      await cb({helper, privateKey});
+    });
+  });
+}
+itEth.only = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {only: true});
+itEth.skip = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {skip: true});
\ No newline at end of file
addedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -0,0 +1,162 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+/* eslint-disable function-call-argument-newline */
+
+import Web3 from 'web3';
+import {WebsocketProvider} from 'web3-core';
+import {Contract} from 'web3-eth-contract';
+
+import {evmToAddress} from '@polkadot/util-crypto';
+import {IKeyringPair} from '@polkadot/types/types';
+
+import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
+
+// Native contracts ABI
+import collectionHelpersAbi from '../../collectionHelpersAbi.json';
+import fungibleAbi from '../../fungibleAbi.json';
+import nonFungibleAbi from '../../nonFungibleAbi.json';
+import refungibleAbi from '../../reFungibleAbi.json';
+import refungibleTokenAbi from '../../reFungibleTokenAbi.json';
+import contractHelpersAbi from './../contractHelpersAbi.json';
+
+class EthGroupBase {
+  helper: EthUniqueHelper;
+
+  constructor(helper: EthUniqueHelper) {
+    this.helper = helper;
+  }
+}
+  
+  
+class NativeContractGroup extends EthGroupBase {
+  DEFAULT_GAS = 2_500_000;
+
+  contractHelpers(caller: string): Contract {
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, gas: this.DEFAULT_GAS});
+  }
+
+  collectionHelpers(caller: string) {
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.DEFAULT_GAS});
+  }
+
+  collection(address: string, mode: 'nft' | 'rft' | 'ft', caller?: string): Contract {
+    const abi = {
+      'nft': nonFungibleAbi,
+      'rft': refungibleAbi,
+      'ft': fungibleAbi
+    }[mode];
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(abi as any, address, {gas: this.DEFAULT_GAS, ...(caller ? {from: caller} : {})});
+  }
+
+  rftTokenByAddress(address: string, caller?: string): Contract {
+    const web3 = this.helper.getWeb3();
+    return new web3.eth.Contract(refungibleTokenAbi as any, address, {gas: this.DEFAULT_GAS, ...(caller ? {from: caller} : {})});
+  }
+
+  rftToken(collectionId: number, tokenId: number, caller?: string): Contract {
+    return this.rftTokenByAddress(this.helper.ethAddress.fromTokenId(collectionId, tokenId), caller);
+  }
+}
+
+  
+class EthGroup extends EthGroupBase {
+  createAccount() {
+    const web3 = this.helper.getWeb3();
+    const account = web3.eth.accounts.create();
+    web3.eth.accounts.wallet.add(account.privateKey);
+    return account.address;
+  }
+
+  async createAccountWithBalance(donor: IKeyringPair, amount=1000n) {
+    const account = this.createAccount();
+    await this.transferBalanceFromSubstrate(donor, account, amount);
+  
+    return account;
+  }
+
+  async transferBalanceFromSubstrate(donor: IKeyringPair, recepient: string, amount=1000n) {
+    return await this.helper.balance.transferToSubstrate(donor, evmToAddress(recepient), amount * this.helper.balance.getOneTokenNominal());
+  }
+
+  async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> {
+    const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+        
+    const result = await collectionHelper.methods.createNonfungibleCollection(name, description, tokenPrefix).send();
+
+    const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
+    const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
+
+    return {collectionId, collectionAddress};
+  }
+}
+  
+  
+class EthAddressGroup extends EthGroupBase {
+  extractCollectionId(address: string): number {
+    if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format');
+    return parseInt(address.substr(address.length - 8), 16);
+  }
+
+  fromCollectionId(collectionId: number): string {
+    if (collectionId >= 0xffffffff || collectionId < 0) throw new Error('collectionId overflow');
+    return Web3.utils.toChecksumAddress(`0x17c4e6453cc49aaaaeaca894e6d9683e${collectionId.toString(16).padStart(8, '0')}`);
+  }
+
+  extractTokenId(address: string): {collectionId: number, tokenId: number} {
+    if (!address.startsWith('0x'))
+      throw 'address not starts with "0x"';
+    if (address.length > 42)
+      throw 'address length is more than 20 bytes';
+    return {
+      collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)),
+      tokenId: Number('0x' + address.substring(address.length - 8)),
+    };
+  }
+
+  fromTokenId(collectionId: number, tokenId: number): string  {
+    return this.helper.util.getNestingTokenAddress(collectionId, tokenId);
+  }
+
+  normalizeAddress(address: string): string {
+    return '0x' + address.substring(address.length - 40);
+  }
+}  
+ 
+
+export class EthUniqueHelper extends DevUniqueHelper {
+  web3: Web3 | null = null;
+  web3Provider: WebsocketProvider | null = null;
+
+  eth: EthGroup;
+  ethAddress: EthAddressGroup;
+  ethNativeContract: NativeContractGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }) {
+    super(logger);
+    this.eth = new EthGroup(this);
+    this.ethAddress = new EthAddressGroup(this);
+    this.ethNativeContract = new NativeContractGroup(this);
+  }
+
+  getWeb3(): Web3 {
+    if(this.web3 === null) throw Error('Web3 not connected');
+    return this.web3;
+  }
+
+  async connectWeb3(wsEndpoint: string) {
+    if(this.web3 !== null) return;
+    this.web3Provider = new Web3.providers.WebsocketProvider(wsEndpoint);
+    this.web3 = new Web3(this.web3Provider);
+  }
+
+  async disconnectWeb3() {
+    if(this.web3 === null) return;
+    this.web3Provider?.connection.close();
+    this.web3 = null;
+  }
+}
+  
\ No newline at end of file