git.delta.rocks / unique-network / refs/commits / 535a8e928ce8

difftreelog

tests: introduce feed alices + refactor tests to use the filename as donor key

Fahrrader2022-10-05parent: #8b875cb.patch.diff
in: master

64 files changed

modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -26,24 +26,25 @@
   "scripts": {
     "lint": "eslint --ext .ts,.js src/",
     "fix": "eslint --ext .ts,.js src/ --fix",
-    "test": "mocha --timeout 9999999 -r ts-node/register './src/**/*.test.ts'",
-    "testEth": "mocha --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'",
-    "testEthMarketplace": "mocha --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'",
-    "testEthNesting": "mocha --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'",
-    "testEthFractionalizer": "mocha --timeout 9999999 -r ts-node/register './**/eth/fractionalizer/**/*.test.ts'",
+    "feedAlices": "ts-node ./src/util/playgrounds/feedAlices.ts",
+
+    "test": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './src/**/*.test.ts'",
+    "testStructure": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",
+    "testEth": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'",
+    "testEthNesting": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'",
+    "testEthFractionalizer": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/fractionalizer/**/*.test.ts'",
+    "testEthMarketplace": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'",
+    "testCollision": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",
+    "testEvent": "yarn feedAlices && mocha --parallel --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",
+    "testRmrk": "mocha --timeout 9999999 -r ts-node/register ./**/rmrk/**.test.ts",
+
     "testEthPayable": "mocha --timeout 9999999 -r ts-node/register './**/eth/payable.test.ts'",
-    "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",
-    "loadTransfer": "ts-node src/transfer.nload.ts",
-    "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts",
-    "testEvent": "mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.test.ts",
+    "testEthTokenProperties": "mocha --timeout 9999999 -r ts-node/register ./**/eth/tokenProperties.test.ts",
     "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",
     "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",
-    "testStructure": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/**.test.ts",
     "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts ./**/getPropertiesRpc.test.ts",
     "testMigration": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",
-    "testRmrk": "mocha --timeout 9999999 -r ts-node/register ./**/rmrk/**.test.ts",
     "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
-    "testSetSchemaVersion": "mocha --timeout 9999999 -r ts-node/register ./**/setSchemaVersion.test.ts",
     "testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",
     "testChangeCollectionOwner": "mocha --timeout 9999999 -r ts-node/register ./**/change-collection-owner.test.ts",
     "testSetCollectionSponsor": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionSponsor.test.ts",
@@ -96,6 +97,10 @@
     "testEthFT": "mocha --timeout 9999999 -r ts-node/register ./**/eth/fungible.test.ts",
     "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",
     "testPromotion": "mocha --timeout 9999999 -r ts-node/register ./**/app-promotion.test.ts",
+
+    "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",
+    "loadTransfer": "ts-node src/transfer.nload.ts",
+    
     "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",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",
     "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
modifiedtests/src/addCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -21,8 +21,8 @@
   let donor: IKeyringPair;
 
   before(async () => {
-    await usingPlaygrounds(async (_, privateKeyWrapper) => {
-      donor = privateKeyWrapper('//Alice');
+    await usingPlaygrounds(async (_, privateKey) => {
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -44,8 +44,8 @@
   let donor: IKeyringPair;
 
   before(async () => {
-    await usingPlaygrounds(async (_, privateKeyWrapper) => {
-      donor = privateKeyWrapper('//Alice');
+    await usingPlaygrounds(async (_, privateKey) => {
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/adminTransferAndBurn.test.tsdiffbeforeafterboth
--- a/tests/src/adminTransferAndBurn.test.ts
+++ b/tests/src/adminTransferAndBurn.test.ts
@@ -24,7 +24,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
modifiedtests/src/allowLists.test.tsdiffbeforeafterboth
--- a/tests/src/allowLists.test.ts
+++ b/tests/src/allowLists.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([30n, 10n, 10n], donor);
     });
   });
@@ -87,7 +87,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([30n, 10n, 10n], donor);
     });
   });
@@ -163,7 +163,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([30n, 10n, 10n], donor);
     });
   });
@@ -291,7 +291,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
before · tests/src/app-promotion.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {19  normalizeAccountId,20  getModuleNames,21  Pallets,22} from './util/helpers';23import {itSub, usingPlaygrounds} from './util/playgrounds';24import {encodeAddress} from '@polkadot/util-crypto';25import {stringToU8a} from '@polkadot/util';26import {SponsoringMode} from './eth/util/helpers';27import {DevUniqueHelper} from './util/playgrounds/unique.dev';28import {itEth, expect} from './eth/util/playgrounds';2930let alice: IKeyringPair;31let palletAdmin: IKeyringPair;32let nominal: bigint;33const palletAddress = calculatePalleteAddress('appstake');34let accounts: IKeyringPair[] = [];35const LOCKING_PERIOD = 20n; // 20 blocks of relay36const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain37const rewardAvailableInBlock = (stakedInBlock: bigint) => {38  if (stakedInBlock % LOCKING_PERIOD === 0n) return stakedInBlock + 20n;39  return (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);40};4142describe('App promotion', () => {43  before(async function () {44    await usingPlaygrounds(async (helper, privateKey) => {45      if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();46      alice = privateKey('//Alice');47      palletAdmin = privateKey('//Charlie'); // TODO use custom address48      await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));49      nominal = helper.balance.getOneTokenNominal();50      await helper.balance.transferToSubstrate(alice, palletAdmin.address, 1000n * nominal);51      await helper.balance.transferToSubstrate(alice, palletAddress, 1000n * nominal);52      accounts = await helper.arrange.createCrowd(100, 1000n, alice); // create accounts-pool to speed up tests53    });54  });5556  describe('stake extrinsic', () => {  57    itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {58      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];59      const totalStakedBefore = await helper.staking.getTotalStaked();60  61      // Minimum stake amount is 100:62      await expect(helper.staking.stake(staker, 100n * nominal - 1n)).to.be.rejected;63      await helper.staking.stake(staker, 100n * nominal);64  65      // Staker balance is: miscFrozen: 100, feeFrozen: 100, reserved: 0n...66      // ...so he can not transfer 90067      expect (await helper.balance.getSubstrateFull(staker.address)).to.contain({miscFrozen: 100n * nominal, feeFrozen: 100n * nominal, reserved: 0n});68      await expect(helper.balance.transferToSubstrate(staker, recepient.address, 900n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');69      70      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(100n * nominal);71      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);72      // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo? 73      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased 74  75      76      await helper.staking.stake(staker, 200n * nominal);77      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal);78      const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});79      expect(totalStakedPerBlock[0].amount).to.equal(100n * nominal);80      expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);81    });82  83    itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {84      const [staker] = await helper.arrange.createAccounts([2000n], alice);85      for (let i = 0; i < 10; i++) {86        await helper.staking.stake(staker, 100n * nominal);87      }88  89      // can have 10 stakes90      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);91      expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);92  93      await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');94  95      // After unstake can stake again96      await helper.staking.unstake(staker);97      await helper.staking.stake(staker, 100n * nominal);98      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);99    });100    101    itSub('should reject transaction if stake amount is more than total free balance minus frozen', async ({helper}) => {102      const staker = accounts.pop()!;103  104      // Can't stake full balance because Alice needs to pay some fee105      await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected;106      await helper.staking.stake(staker, 500n * nominal);107  108      // Can't stake 500 tkn because Alice has Less than 500 transferable;109      await expect(helper.staking.stake(staker, 500n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions'); 110      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(500n * nominal);111    });112    113    itSub('for different accounts in one block is possible', async ({helper}) => {114      const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];115        116      const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));117      await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;118  119      const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));120      expect(crowdStakes).to.deep.equal([100n * nominal, 100n * nominal, 100n * nominal, 100n * nominal]);121    });122  });123  124  describe('unstake extrinsic', () => {  125    itSub('should change balance state from "frozen" to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async ({helper}) => {126      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];127      const totalStakedBefore = await helper.staking.getTotalStaked();128      await helper.staking.stake(staker, 900n * nominal);129      await helper.staking.unstake(staker);130  131      // Right after unstake balance is reserved132      // Staker can not transfer 133      expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 900n * nominal, miscFrozen: 0n, feeFrozen: 0n});134      await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejectedWith('balances.InsufficientBalance');135      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(900n * nominal);136      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);137      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);138    });139  140    itSub('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async ({helper}) => {141      const staker = accounts.pop()!;142      await helper.staking.stake(staker, 100n * nominal);143      await helper.staking.unstake(staker);144      const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});145  146      // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n147      await helper.wait.forParachainBlockNumber(pendingUnstake.block);148      expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});149      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);150  151      // staker can transfer:152      await helper.balance.transferToSubstrate(staker, alice.address, 998n * nominal);153      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);154    });155  156    itSub('should successfully unstake multiple stakes', async ({helper}) => {157      const staker = accounts.pop()!;158      await helper.staking.stake(staker, 100n * nominal);159      await helper.staking.stake(staker, 200n * nominal);160      await helper.staking.stake(staker, 300n * nominal);161  162      // staked: [100, 200, 300]; unstaked: 0163      let totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});164      let pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});165      let stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});166      expect(totalPendingUnstake).to.be.deep.equal(0n);167      expect(pendingUnstake).to.be.deep.equal([]);168      expect(stakes[0].amount).to.equal(100n * nominal);169      expect(stakes[1].amount).to.equal(200n * nominal);170      expect(stakes[2].amount).to.equal(300n * nominal);171       172      // Can unstake multiple stakes173      await helper.staking.unstake(staker);174      pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});175      totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});176      stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});177      expect(totalPendingUnstake).to.be.equal(600n * nominal);178      expect(stakes).to.be.deep.equal([]);179      expect(pendingUnstake[0].amount).to.equal(600n * nominal);180  181      expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 600n * nominal, feeFrozen: 0n, miscFrozen: 0n});182      await helper.wait.forParachainBlockNumber(pendingUnstake[0].block);183      expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});184      expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);185    });186  187    itSub('should not have any effects if no active stakes', async ({helper}) => {188      const staker = accounts.pop()!;189        190      // unstake has no effect if no stakes at all191      await helper.staking.unstake(staker);192      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);193      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper194  195      // TODO stake() unstake() waitUnstaked() unstake();196  197      // can't unstake if there are only pendingUnstakes198      await helper.staking.stake(staker, 100n * nominal);199      await helper.staking.unstake(staker);200      await helper.staking.unstake(staker);201  202      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);203      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);204    });205  206    itSub('should keep different unlocking block for each unlocking stake', async ({helper}) => {207      const staker = accounts.pop()!;208      await helper.staking.stake(staker, 100n * nominal);209      await helper.staking.unstake(staker);210      await helper.staking.stake(staker, 120n * nominal);211      await helper.staking.unstake(staker);212  213      const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});214      expect(unstakingPerBlock).has.length(2);215      expect(unstakingPerBlock[0].amount).to.equal(100n * nominal);216      expect(unstakingPerBlock[1].amount).to.equal(120n * nominal);217    });218  219    itSub('should be possible for different accounts in one block', async ({helper}) => {220      const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];221  222      await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));223      await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));224  225      await Promise.all(stakers.map(async (staker) => {226        expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);227        expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);228      }));229    });230  });231  232  describe('admin adress', () => {233    itSub('can be set by sudo only', async ({helper}) => {234      const nonAdmin = accounts.pop()!;235      // nonAdmin can not set admin not from himself nor as a sudo236      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected;237      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected;238  239      // Alice can240      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;241    });242    243    itSub('can be any valid CrossAccountId', async ({helper}) => {244      // We are not going to set an eth address as a sponsor,245      // but we do want to check, it doesn't break anything;246      const account = accounts.pop()!;247      const ethAccount = helper.address.substrateToEth(account.address); 248      // Alice sets Ethereum address as a sudo. Then Substrate address back...249      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled;250      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;251        252      // ...It doesn't break anything;253      const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});254      await expect(helper.signTransaction(account, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;255    });256  257    itSub('can be reassigned', async ({helper}) => {258      const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];259      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});260        261      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(oldAdmin))))).to.be.fulfilled;262      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(newAdmin))))).to.be.fulfilled;263      await expect(helper.signTransaction(oldAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;264        265      await expect(helper.signTransaction(newAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;266    });267  });268  269  describe('collection sponsoring', () => {270    before(async function () {271      await usingPlaygrounds(async (helper) => {272        const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}));273        await helper.signTransaction(alice, tx);274      });275    });276  277    itSub('should actually sponsor transactions', async ({helper}) => {278      const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];279      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});280      const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});281      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));282      const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);283  284      await token.transfer(tokenSender, {Substrate: receiver.address});285      expect (await token.getOwner()).to.be.deep.equal({Substrate: receiver.address});286      const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);287  288      // senders balance the same, transaction has sponsored289      expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);290      expect (palletBalanceBefore > palletBalanceAfter).to.be.true;291    });292  293    itSub('can not be set by non admin', async ({helper}) => {294      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];295  296      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});297        298      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;299      expect((await collection.getData())?.raw.sponsorship).to.equal('Disabled');300    });301  302    itSub('should set pallet address as confirmed admin', async ({helper}) => {303      const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];304        305      // Can set sponsoring for collection without sponsor306      const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});307      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.fulfilled;308      expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});309  310      // Can set sponsoring for collection with unconfirmed sponsor311      const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});312      expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address});313      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled;314      expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});315  316      // Can set sponsoring for collection with confirmed sponsor317      const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});318      await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor);319      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled;320      expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});321    });322  323    itSub('can be overwritten by collection owner', async ({helper}) => {    324      const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];325      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});326      const collectionId = collection.collectionId;327        328      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionId))).to.be.fulfilled;329        330      // Collection limits still can be changed by the owner331      expect(await collection.setLimits(collectionOwner, {sponsorTransferTimeout: 0})).to.be.true;332      expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);333      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});334  335      // Collection sponsor can be changed too336      expect((await collection.setSponsor(collectionOwner, newSponsor.address))).to.be.true;337      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: newSponsor.address});338    });339    340    itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {341      const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};342      const collectionWithLimits = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});343  344      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;345      expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);346    });347    348    itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {349      const collectionOwner = accounts.pop()!;350        351      // collection has never existed352      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;353      // collection has been burned354      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});355      await collection.burn(collectionOwner);356  357      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;358    });359  });360  361  describe('stopSponsoringCollection', () => {362    itSub('can not be called by non-admin', async ({helper}) => {    363      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];364      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});365        366      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;367        368      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;369      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});370    });371  372    itSub('should set sponsoring as disabled', async ({helper}) => {373      const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];374      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});375      const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});376        377      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));378      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId));379        380      expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');381  382      // Transactions are not sponsored anymore:383      const ownerBalanceBefore = await helper.balance.getSubstrate(collectionOwner.address);384      await token.transfer(collectionOwner, {Substrate: recepient.address});385      const ownerBalanceAfter = await helper.balance.getSubstrate(collectionOwner.address);386      expect(ownerBalanceAfter < ownerBalanceBefore).to.be.equal(true);387    });388  389    itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {390      const collectionOwner = accounts.pop()!;391      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});392      await collection.confirmSponsorship(collectionOwner);393        394      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;395        396      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: collectionOwner.address});397    });398  399    itSub('should reject transaction if collection does not exist', async ({helper}) => {    400      const collectionOwner = accounts.pop()!;401      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});402        403      await collection.burn(collectionOwner);404      await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [collection.collectionId], true)).to.be.rejectedWith('common.CollectionNotFound');405      await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [999_999_999], true)).to.be.rejectedWith('common.CollectionNotFound');406    });407  });408  409  describe('contract sponsoring', () => {410    itEth('should set palletes address as a sponsor', async ({helper}) => {411      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();412      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);413      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);414  415      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);416      417      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true;  418      expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  419      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({420        confirmed: {421          substrate: palletAddress,422        },423      });424    });425  426    itEth('should overwrite sponsoring mode and existed sponsor', async ({helper}) => {427      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();428      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);429      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);430  431      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;432  433      // Contract is self sponsored434      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.be.deep.equal({435        confirmed: {436          ethereum: flipper.options.address.toLowerCase(),437        },438      });439  440      // set promotion sponsoring441      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);442  443      // new sponsor is pallet address444      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true;  445      expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner);  446      expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({447        confirmed: {448          substrate: palletAddress,449        },450      });451    });452  453    itEth('can be overwritten by contract owner', async ({helper}) => {454      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();455      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);456      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);457  458      // contract sponsored by pallet459      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);460  461      // owner sets self sponsoring462      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;463  464      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true;  465      expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  466      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({467        confirmed: {468          ethereum: flipper.options.address.toLowerCase(),469        },470      });471    });472  473    itEth('can not be set by non admin', async ({helper}) => {474      const nonAdmin = accounts.pop()!;475      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();476      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);477      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);478  479      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;480  481      // nonAdmin calls sponsorContract482      await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');483  484      // contract still self-sponsored 485      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({486        confirmed: {487          ethereum: flipper.options.address.toLowerCase(),488        },489      });490    });491  492    itEth('should actually sponsor transactions', async ({helper}) => {493      // Contract caller494      const caller = await helper.eth.createAccountWithBalance(alice, 1000n);495      const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);496          497      // Deploy flipper498      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();499      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);500      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);501      502      // Owner sets to sponsor every tx503      await contractHelper.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: contractOwner});504      await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});505      await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address, 1000n); // transferBalanceToEth(api, alice, flipper.options.address, 1000n);506  507      // Set promotion to the Flipper508      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorContract(flipper.options.address));509  510      // Caller calls Flipper511      await flipper.methods.flip().send({from: caller});512      expect(await flipper.methods.getValue().call()).to.be.true;513  514      // The contracts and caller balances have not changed515      const callerBalance = await helper.balance.getEthereum(caller);516      const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);517      expect(callerBalance).to.be.equal(1000n * nominal);518      expect(1000n * nominal === contractBalanceAfter).to.be.true;519  520      // The pallet balance has decreased521      const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);522      expect(palletBalanceAfter < palletBalanceBefore).to.be.true;523    });524  });525  526  describe('stopSponsoringContract', () => {  527    itEth('should remove pallet address from contract sponsors', async ({helper}) => {528      const caller = await helper.eth.createAccountWithBalance(alice, 1000n);529      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();530      const flipper = await helper.eth.deployFlipper(contractOwner);531      await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address);532      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);533  534      await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});535      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);536      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true);537  538      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.false;  539      expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  540      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({541        disabled: null,542      });543  544      await flipper.methods.flip().send({from: caller});545      expect(await flipper.methods.getValue().call()).to.be.true;546  547      const callerBalance = await helper.balance.getEthereum(caller);548      const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);549  550      // caller payed for call551      expect(1000n * nominal > callerBalance).to.be.true;552      expect(contractBalanceAfter).to.be.equal(1000n * nominal);553    });554  555    itEth('can not be called by non-admin', async ({helper}) => {556      const nonAdmin = accounts.pop()!;557      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();558      const flipper = await helper.eth.deployFlipper(contractOwner);559  560      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);561      await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address]))562        .to.be.rejectedWith(/appPromotion\.NoPermission/);563    });564  565    itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {566      const nonAdmin = accounts.pop()!;567      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();568      const flipper = await helper.eth.deployFlipper(contractOwner);569      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);570      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;571  572      await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');573    });574  });575  576  describe('rewards', () => {577    itSub('can not be called by non admin', async ({helper}) => {578      const nonAdmin = accounts.pop()!;579      await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');580    });581  582    itSub('should increase total staked', async ({helper}) => {583      const staker = accounts.pop()!;584      const totalStakedBefore = await helper.staking.getTotalStaked();585      await helper.staking.stake(staker, 100n * nominal);586  587      // Wait for rewards and pay588      const [stakedInBlock] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});589      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock.block));590      const totalPayout = (await helper.admin.payoutStakers(palletAdmin, 100)).reduce((prev, payout) => prev + payout.payout, 0n);591  592      const totalStakedAfter = await helper.staking.getTotalStaked();593      expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);594      // staker can unstake595      await helper.staking.unstake(staker);596      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal, 10n));597    });598  599    itSub('should credit 0.05% for staking period', async ({helper}) => {    600      const staker = accounts.pop()!;601  602      await waitPromotionPeriodDoesntEnd(helper);603        604      await helper.staking.stake(staker, 100n * nominal);605      await helper.staking.stake(staker, 200n * nominal);606  607      // wait rewards are available:608      const [_, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});609      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));610  611      const payoutToStaker = (await helper.admin.payoutStakers(palletAdmin, 100)).find((payout) => payout.staker === staker.address)?.payout;612      expect(payoutToStaker + 300n * nominal).to.equal(calculateIncome(300n * nominal, 10n));613  614      const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});615      expect(totalStakedPerBlock[0].amount).to.equal(calculateIncome(100n * nominal, 10n));616      expect(totalStakedPerBlock[1].amount).to.equal(calculateIncome(200n * nominal, 10n));617    });618  619    itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {620      const staker = accounts.pop()!;621  622      await helper.staking.stake(staker, 100n * nominal);623      // wait for two rewards are available:624      let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});625      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);626  627      await helper.admin.payoutStakers(palletAdmin, 100);628      [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});629      const frozenBalanceShouldBe = calculateIncome(100n * nominal, 10n, 2);630      expect(stake.amount).to.be.equal(frozenBalanceShouldBe);631  632      const stakerFullBalance = await helper.balance.getSubstrateFull(staker.address);633  634      expect(stakerFullBalance).to.contain({reserved: 0n, feeFrozen: frozenBalanceShouldBe, miscFrozen: frozenBalanceShouldBe});635    });636    637    itSub('should not be credited for unstaked (reserved) balance', async ({helper}) => {638      // staker unstakes before rewards has been payed639      const staker = accounts.pop()!;640      await helper.staking.stake(staker, 100n * nominal);641      const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});642      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);643      await helper.staking.unstake(staker);644        645      // so he did not receive any rewards646      const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);647      await helper.admin.payoutStakers(palletAdmin, 100);648      const totalBalanceAfter = await helper.balance.getSubstrate(staker.address);649  650      expect(totalBalanceBefore).to.be.equal(totalBalanceAfter);651    });652    653    itSub('should bring compound interest', async ({helper}) => {654      const staker = accounts.pop()!;655              656      await helper.staking.stake(staker, 100n * nominal);657  658      let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});659      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));660        661      await helper.admin.payoutStakers(palletAdmin, 100);662      [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});663      expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n));664        665      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);666      await helper.admin.payoutStakers(palletAdmin, 100);667      [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});668      expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n, 2));669    });670  671    itSub.skip('can be paid 1000 rewards in a time', async ({helper}) => {672      // all other stakes should be unstaked673      const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, alice);674  675      // stakers stakes 10 times each676      for (let i = 0; i < 10; i++) {677        await Promise.all(oneHundredStakers.map(staker => helper.staking.stake(staker, 100n * nominal)));678      }679      await helper.wait.newBlocks(40);680      await helper.admin.payoutStakers(palletAdmin, 100);681    });682  683    itSub.skip('can handle 40.000 rewards', async ({helper}) => {684      const [donor] = await helper.arrange.createAccounts([7_000_000n], alice);685      const crowdStakes = async () => {686        // each account in the crowd stakes 2 times687        const crowd = await helper.arrange.createCrowd(500, 300n, donor);688        await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));689        await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));690        // 691      };692  693      for (let i = 0; i < 40; i++) {694        await crowdStakes();695      }696  697      // TODO pay rewards for some period698    });699  });700});701702function calculatePalleteAddress(palletId: any) {703  const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));704  return encodeAddress(address);705}706707function calculateIncome(base: bigint, calcPeriod: bigint, iter = 0): bigint {708  const DAY = 7200n;709  const ACCURACY = 1_000_000_000n;710  const income = base + base * (ACCURACY * (calcPeriod * 5n) / (10_000n * DAY)) / ACCURACY ;711  712  if (iter > 1) {713    return calculateIncome(income, calcPeriod, iter - 1);714  } else return income;715}716717// Wait while promotion period less than specified block, to avoid boundary cases718// 0 if this should be the beginning of the period.719async function waitPromotionPeriodDoesntEnd(helper: DevUniqueHelper, waitBlockLessThan = LOCKING_PERIOD / 3n) {720  const relayBlockNumber = (await helper.api!.query.parachainSystem.validationData()).value.relayParentNumber.toNumber(); // await helper.chain.getLatestBlockNumber();721  const currentPeriodBlock = BigInt(relayBlockNumber) % LOCKING_PERIOD;722723  if (currentPeriodBlock > waitBlockLessThan) {724    await helper.wait.forRelayBlockNumber(BigInt(relayBlockNumber) + LOCKING_PERIOD - currentPeriodBlock);725  }726}
after · tests/src/app-promotion.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {19  normalizeAccountId,20  getModuleNames,21  Pallets,22} from './util/helpers';23import {itSub, usingPlaygrounds} from './util/playgrounds';24import {encodeAddress} from '@polkadot/util-crypto';25import {stringToU8a} from '@polkadot/util';26import {SponsoringMode} from './eth/util/helpers';27import {DevUniqueHelper} from './util/playgrounds/unique.dev';28import {itEth, expect} from './eth/util/playgrounds';2930let alice: IKeyringPair;31let palletAdmin: IKeyringPair;32let nominal: bigint;33const palletAddress = calculatePalleteAddress('appstake');34let accounts: IKeyringPair[] = [];35const LOCKING_PERIOD = 20n; // 20 blocks of relay36const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain37const rewardAvailableInBlock = (stakedInBlock: bigint) => {38  if (stakedInBlock % LOCKING_PERIOD === 0n) return stakedInBlock + 20n;39  return (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);40};4142describe('App promotion', () => {43  before(async function () {44    await usingPlaygrounds(async (helper, privateKey) => {45      if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();46      alice = await privateKey({filename: __filename});47      [palletAdmin] = await helper.arrange.createAccounts([100n], alice);48      await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));49      nominal = helper.balance.getOneTokenNominal();50      await helper.balance.transferToSubstrate(alice, palletAdmin.address, 1000n * nominal);51      await helper.balance.transferToSubstrate(alice, palletAddress, 1000n * nominal);52      accounts = await helper.arrange.createCrowd(100, 1000n, alice); // create accounts-pool to speed up tests53    });54  });5556  describe('stake extrinsic', () => {  57    itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {58      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];59      const totalStakedBefore = await helper.staking.getTotalStaked();60  61      // Minimum stake amount is 100:62      await expect(helper.staking.stake(staker, 100n * nominal - 1n)).to.be.rejected;63      await helper.staking.stake(staker, 100n * nominal);64  65      // Staker balance is: miscFrozen: 100, feeFrozen: 100, reserved: 0n...66      // ...so he can not transfer 90067      expect (await helper.balance.getSubstrateFull(staker.address)).to.contain({miscFrozen: 100n * nominal, feeFrozen: 100n * nominal, reserved: 0n});68      await expect(helper.balance.transferToSubstrate(staker, recepient.address, 900n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');69      70      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(100n * nominal);71      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);72      // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo? 73      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased 74  75      76      await helper.staking.stake(staker, 200n * nominal);77      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal);78      const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});79      expect(totalStakedPerBlock[0].amount).to.equal(100n * nominal);80      expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);81    });82  83    itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {84      const [staker] = await helper.arrange.createAccounts([2000n], alice);85      for (let i = 0; i < 10; i++) {86        await helper.staking.stake(staker, 100n * nominal);87      }88  89      // can have 10 stakes90      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);91      expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);92  93      await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');94  95      // After unstake can stake again96      await helper.staking.unstake(staker);97      await helper.staking.stake(staker, 100n * nominal);98      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);99    });100    101    itSub('should reject transaction if stake amount is more than total free balance minus frozen', async ({helper}) => {102      const staker = accounts.pop()!;103  104      // Can't stake full balance because Alice needs to pay some fee105      await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected;106      await helper.staking.stake(staker, 500n * nominal);107  108      // Can't stake 500 tkn because Alice has Less than 500 transferable;109      await expect(helper.staking.stake(staker, 500n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions'); 110      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(500n * nominal);111    });112    113    itSub('for different accounts in one block is possible', async ({helper}) => {114      const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];115        116      const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));117      await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;118  119      const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));120      expect(crowdStakes).to.deep.equal([100n * nominal, 100n * nominal, 100n * nominal, 100n * nominal]);121    });122  });123  124  describe('unstake extrinsic', () => {  125    itSub('should change balance state from "frozen" to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async ({helper}) => {126      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];127      const totalStakedBefore = await helper.staking.getTotalStaked();128      await helper.staking.stake(staker, 900n * nominal);129      await helper.staking.unstake(staker);130  131      // Right after unstake balance is reserved132      // Staker can not transfer 133      expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 900n * nominal, miscFrozen: 0n, feeFrozen: 0n});134      await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejectedWith('balances.InsufficientBalance');135      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(900n * nominal);136      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);137      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);138    });139  140    itSub('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async ({helper}) => {141      const staker = accounts.pop()!;142      await helper.staking.stake(staker, 100n * nominal);143      await helper.staking.unstake(staker);144      const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});145  146      // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n147      await helper.wait.forParachainBlockNumber(pendingUnstake.block);148      expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});149      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);150  151      // staker can transfer:152      await helper.balance.transferToSubstrate(staker, alice.address, 998n * nominal);153      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);154    });155  156    itSub('should successfully unstake multiple stakes', async ({helper}) => {157      const staker = accounts.pop()!;158      await helper.staking.stake(staker, 100n * nominal);159      await helper.staking.stake(staker, 200n * nominal);160      await helper.staking.stake(staker, 300n * nominal);161  162      // staked: [100, 200, 300]; unstaked: 0163      let totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});164      let pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});165      let stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});166      expect(totalPendingUnstake).to.be.deep.equal(0n);167      expect(pendingUnstake).to.be.deep.equal([]);168      expect(stakes[0].amount).to.equal(100n * nominal);169      expect(stakes[1].amount).to.equal(200n * nominal);170      expect(stakes[2].amount).to.equal(300n * nominal);171       172      // Can unstake multiple stakes173      await helper.staking.unstake(staker);174      pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});175      totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});176      stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});177      expect(totalPendingUnstake).to.be.equal(600n * nominal);178      expect(stakes).to.be.deep.equal([]);179      expect(pendingUnstake[0].amount).to.equal(600n * nominal);180  181      expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 600n * nominal, feeFrozen: 0n, miscFrozen: 0n});182      await helper.wait.forParachainBlockNumber(pendingUnstake[0].block);183      expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});184      expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);185    });186  187    itSub('should not have any effects if no active stakes', async ({helper}) => {188      const staker = accounts.pop()!;189        190      // unstake has no effect if no stakes at all191      await helper.staking.unstake(staker);192      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);193      expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper194  195      // TODO stake() unstake() waitUnstaked() unstake();196  197      // can't unstake if there are only pendingUnstakes198      await helper.staking.stake(staker, 100n * nominal);199      await helper.staking.unstake(staker);200      await helper.staking.unstake(staker);201  202      expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);203      expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);204    });205  206    itSub('should keep different unlocking block for each unlocking stake', async ({helper}) => {207      const staker = accounts.pop()!;208      await helper.staking.stake(staker, 100n * nominal);209      await helper.staking.unstake(staker);210      await helper.staking.stake(staker, 120n * nominal);211      await helper.staking.unstake(staker);212  213      const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});214      expect(unstakingPerBlock).has.length(2);215      expect(unstakingPerBlock[0].amount).to.equal(100n * nominal);216      expect(unstakingPerBlock[1].amount).to.equal(120n * nominal);217    });218  219    itSub('should be possible for different accounts in one block', async ({helper}) => {220      const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];221  222      await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));223      await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));224  225      await Promise.all(stakers.map(async (staker) => {226        expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);227        expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);228      }));229    });230  });231  232  describe('admin adress', () => {233    itSub('can be set by sudo only', async ({helper}) => {234      const nonAdmin = accounts.pop()!;235      // nonAdmin can not set admin not from himself nor as a sudo236      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected;237      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected;238  239      // Alice can240      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;241    });242    243    itSub('can be any valid CrossAccountId', async ({helper}) => {244      // We are not going to set an eth address as a sponsor,245      // but we do want to check, it doesn't break anything;246      const account = accounts.pop()!;247      const ethAccount = helper.address.substrateToEth(account.address); 248      // Alice sets Ethereum address as a sudo. Then Substrate address back...249      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled;250      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;251        252      // ...It doesn't break anything;253      const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});254      await expect(helper.signTransaction(account, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;255    });256  257    itSub('can be reassigned', async ({helper}) => {258      const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];259      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});260        261      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(oldAdmin))))).to.be.fulfilled;262      await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(newAdmin))))).to.be.fulfilled;263      await expect(helper.signTransaction(oldAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;264        265      await expect(helper.signTransaction(newAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;266    });267  });268  269  describe('collection sponsoring', () => {270    before(async function () {271      await usingPlaygrounds(async (helper) => {272        const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}));273        await helper.signTransaction(alice, tx);274      });275    });276  277    itSub('should actually sponsor transactions', async ({helper}) => {278      const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];279      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});280      const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});281      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));282      const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);283  284      await token.transfer(tokenSender, {Substrate: receiver.address});285      expect (await token.getOwner()).to.be.deep.equal({Substrate: receiver.address});286      const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);287  288      // senders balance the same, transaction has sponsored289      expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);290      expect (palletBalanceBefore > palletBalanceAfter).to.be.true;291    });292  293    itSub('can not be set by non admin', async ({helper}) => {294      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];295  296      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});297        298      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;299      expect((await collection.getData())?.raw.sponsorship).to.equal('Disabled');300    });301  302    itSub('should set pallet address as confirmed admin', async ({helper}) => {303      const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];304        305      // Can set sponsoring for collection without sponsor306      const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});307      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.fulfilled;308      expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});309  310      // Can set sponsoring for collection with unconfirmed sponsor311      const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});312      expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address});313      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled;314      expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});315  316      // Can set sponsoring for collection with confirmed sponsor317      const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});318      await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor);319      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled;320      expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});321    });322  323    itSub('can be overwritten by collection owner', async ({helper}) => {    324      const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];325      const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});326      const collectionId = collection.collectionId;327        328      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionId))).to.be.fulfilled;329        330      // Collection limits still can be changed by the owner331      expect(await collection.setLimits(collectionOwner, {sponsorTransferTimeout: 0})).to.be.true;332      expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);333      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});334  335      // Collection sponsor can be changed too336      expect((await collection.setSponsor(collectionOwner, newSponsor.address))).to.be.true;337      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: newSponsor.address});338    });339    340    itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {341      const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};342      const collectionWithLimits = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});343  344      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;345      expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);346    });347    348    itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {349      const collectionOwner = accounts.pop()!;350        351      // collection has never existed352      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;353      // collection has been burned354      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});355      await collection.burn(collectionOwner);356  357      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;358    });359  });360  361  describe('stopSponsoringCollection', () => {362    itSub('can not be called by non-admin', async ({helper}) => {    363      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];364      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});365        366      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;367        368      await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;369      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});370    });371  372    itSub('should set sponsoring as disabled', async ({helper}) => {373      const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];374      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});375      const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});376        377      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));378      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId));379        380      expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');381  382      // Transactions are not sponsored anymore:383      const ownerBalanceBefore = await helper.balance.getSubstrate(collectionOwner.address);384      await token.transfer(collectionOwner, {Substrate: recepient.address});385      const ownerBalanceAfter = await helper.balance.getSubstrate(collectionOwner.address);386      expect(ownerBalanceAfter < ownerBalanceBefore).to.be.equal(true);387    });388  389    itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {390      const collectionOwner = accounts.pop()!;391      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});392      await collection.confirmSponsorship(collectionOwner);393        394      await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;395        396      expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: collectionOwner.address});397    });398  399    itSub('should reject transaction if collection does not exist', async ({helper}) => {    400      const collectionOwner = accounts.pop()!;401      const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});402        403      await collection.burn(collectionOwner);404      await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [collection.collectionId], true)).to.be.rejectedWith('common.CollectionNotFound');405      await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [999_999_999], true)).to.be.rejectedWith('common.CollectionNotFound');406    });407  });408  409  describe('contract sponsoring', () => {410    itEth('should set palletes address as a sponsor', async ({helper}) => {411      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();412      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);413      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);414  415      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);416      417      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true;  418      expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  419      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({420        confirmed: {421          substrate: palletAddress,422        },423      });424    });425  426    itEth('should overwrite sponsoring mode and existed sponsor', async ({helper}) => {427      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();428      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);429      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);430  431      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;432  433      // Contract is self sponsored434      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.be.deep.equal({435        confirmed: {436          ethereum: flipper.options.address.toLowerCase(),437        },438      });439  440      // set promotion sponsoring441      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);442  443      // new sponsor is pallet address444      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true;  445      expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner);  446      expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({447        confirmed: {448          substrate: palletAddress,449        },450      });451    });452  453    itEth('can be overwritten by contract owner', async ({helper}) => {454      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();455      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);456      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);457  458      // contract sponsored by pallet459      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);460  461      // owner sets self sponsoring462      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;463  464      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true;  465      expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  466      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({467        confirmed: {468          ethereum: flipper.options.address.toLowerCase(),469        },470      });471    });472  473    itEth('can not be set by non admin', async ({helper}) => {474      const nonAdmin = accounts.pop()!;475      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();476      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);477      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);478  479      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;480  481      // nonAdmin calls sponsorContract482      await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');483  484      // contract still self-sponsored 485      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({486        confirmed: {487          ethereum: flipper.options.address.toLowerCase(),488        },489      });490    });491  492    itEth('should actually sponsor transactions', async ({helper}) => {493      // Contract caller494      const caller = await helper.eth.createAccountWithBalance(alice, 1000n);495      const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);496          497      // Deploy flipper498      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();499      const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);500      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);501      502      // Owner sets to sponsor every tx503      await contractHelper.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: contractOwner});504      await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});505      await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address, 1000n); // transferBalanceToEth(api, alice, flipper.options.address, 1000n);506  507      // Set promotion to the Flipper508      await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorContract(flipper.options.address));509  510      // Caller calls Flipper511      await flipper.methods.flip().send({from: caller});512      expect(await flipper.methods.getValue().call()).to.be.true;513  514      // The contracts and caller balances have not changed515      const callerBalance = await helper.balance.getEthereum(caller);516      const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);517      expect(callerBalance).to.be.equal(1000n * nominal);518      expect(1000n * nominal === contractBalanceAfter).to.be.true;519  520      // The pallet balance has decreased521      const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);522      expect(palletBalanceAfter < palletBalanceBefore).to.be.true;523    });524  });525  526  describe('stopSponsoringContract', () => {  527    itEth('should remove pallet address from contract sponsors', async ({helper}) => {528      const caller = await helper.eth.createAccountWithBalance(alice, 1000n);529      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();530      const flipper = await helper.eth.deployFlipper(contractOwner);531      await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address);532      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);533  534      await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});535      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);536      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true);537  538      expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.false;  539      expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner);  540      expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({541        disabled: null,542      });543  544      await flipper.methods.flip().send({from: caller});545      expect(await flipper.methods.getValue().call()).to.be.true;546  547      const callerBalance = await helper.balance.getEthereum(caller);548      const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);549  550      // caller payed for call551      expect(1000n * nominal > callerBalance).to.be.true;552      expect(contractBalanceAfter).to.be.equal(1000n * nominal);553    });554  555    itEth('can not be called by non-admin', async ({helper}) => {556      const nonAdmin = accounts.pop()!;557      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();558      const flipper = await helper.eth.deployFlipper(contractOwner);559  560      await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);561      await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address]))562        .to.be.rejectedWith(/appPromotion\.NoPermission/);563    });564  565    itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {566      const nonAdmin = accounts.pop()!;567      const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();568      const flipper = await helper.eth.deployFlipper(contractOwner);569      const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);570      await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;571  572      await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');573    });574  });575  576  describe('rewards', () => {577    itSub('can not be called by non admin', async ({helper}) => {578      const nonAdmin = accounts.pop()!;579      await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');580    });581  582    itSub('should increase total staked', async ({helper}) => {583      const staker = accounts.pop()!;584      const totalStakedBefore = await helper.staking.getTotalStaked();585      await helper.staking.stake(staker, 100n * nominal);586  587      // Wait for rewards and pay588      const [stakedInBlock] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});589      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock.block));590      const totalPayout = (await helper.admin.payoutStakers(palletAdmin, 100)).reduce((prev, payout) => prev + payout.payout, 0n);591  592      const totalStakedAfter = await helper.staking.getTotalStaked();593      expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);594      // staker can unstake595      await helper.staking.unstake(staker);596      expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal, 10n));597    });598  599    itSub('should credit 0.05% for staking period', async ({helper}) => {    600      const staker = accounts.pop()!;601  602      await waitPromotionPeriodDoesntEnd(helper);603        604      await helper.staking.stake(staker, 100n * nominal);605      await helper.staking.stake(staker, 200n * nominal);606  607      // wait rewards are available:608      const [_, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});609      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));610  611      const payoutToStaker = (await helper.admin.payoutStakers(palletAdmin, 100)).find((payout) => payout.staker === staker.address)?.payout;612      expect(payoutToStaker + 300n * nominal).to.equal(calculateIncome(300n * nominal, 10n));613  614      const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});615      expect(totalStakedPerBlock[0].amount).to.equal(calculateIncome(100n * nominal, 10n));616      expect(totalStakedPerBlock[1].amount).to.equal(calculateIncome(200n * nominal, 10n));617    });618  619    itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {620      const staker = accounts.pop()!;621  622      await helper.staking.stake(staker, 100n * nominal);623      // wait for two rewards are available:624      let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});625      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);626  627      await helper.admin.payoutStakers(palletAdmin, 100);628      [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});629      const frozenBalanceShouldBe = calculateIncome(100n * nominal, 10n, 2);630      expect(stake.amount).to.be.equal(frozenBalanceShouldBe);631  632      const stakerFullBalance = await helper.balance.getSubstrateFull(staker.address);633  634      expect(stakerFullBalance).to.contain({reserved: 0n, feeFrozen: frozenBalanceShouldBe, miscFrozen: frozenBalanceShouldBe});635    });636    637    itSub('should not be credited for unstaked (reserved) balance', async ({helper}) => {638      // staker unstakes before rewards has been payed639      const staker = accounts.pop()!;640      await helper.staking.stake(staker, 100n * nominal);641      const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});642      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);643      await helper.staking.unstake(staker);644        645      // so he did not receive any rewards646      const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);647      await helper.admin.payoutStakers(palletAdmin, 100);648      const totalBalanceAfter = await helper.balance.getSubstrate(staker.address);649  650      expect(totalBalanceBefore).to.be.equal(totalBalanceAfter);651    });652    653    itSub('should bring compound interest', async ({helper}) => {654      const staker = accounts.pop()!;655              656      await helper.staking.stake(staker, 100n * nominal);657  658      let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});659      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));660        661      await helper.admin.payoutStakers(palletAdmin, 100);662      [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});663      expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n));664        665      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);666      await helper.admin.payoutStakers(palletAdmin, 100);667      [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});668      expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n, 2));669    });670  671    itSub.skip('can be paid 1000 rewards in a time', async ({helper}) => {672      // all other stakes should be unstaked673      const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, alice);674  675      // stakers stakes 10 times each676      for (let i = 0; i < 10; i++) {677        await Promise.all(oneHundredStakers.map(staker => helper.staking.stake(staker, 100n * nominal)));678      }679      await helper.wait.newBlocks(40);680      await helper.admin.payoutStakers(palletAdmin, 100);681    });682  683    itSub.skip('can handle 40.000 rewards', async ({helper}) => {684      const [donor] = await helper.arrange.createAccounts([7_000_000n], alice);685      const crowdStakes = async () => {686        // each account in the crowd stakes 2 times687        const crowd = await helper.arrange.createCrowd(500, 300n, donor);688        await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));689        await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));690        // 691      };692  693      for (let i = 0; i < 40; i++) {694        await crowdStakes();695      }696  697      // TODO pay rewards for some period698    });699  });700});701702function calculatePalleteAddress(palletId: any) {703  const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));704  return encodeAddress(address);705}706707function calculateIncome(base: bigint, calcPeriod: bigint, iter = 0): bigint {708  const DAY = 7200n;709  const ACCURACY = 1_000_000_000n;710  const income = base + base * (ACCURACY * (calcPeriod * 5n) / (10_000n * DAY)) / ACCURACY ;711  712  if (iter > 1) {713    return calculateIncome(income, calcPeriod, iter - 1);714  } else return income;715}716717// Wait while promotion period less than specified block, to avoid boundary cases718// 0 if this should be the beginning of the period.719async function waitPromotionPeriodDoesntEnd(helper: DevUniqueHelper, waitBlockLessThan = LOCKING_PERIOD / 3n) {720  const relayBlockNumber = (await helper.api!.query.parachainSystem.validationData()).value.relayParentNumber.toNumber(); // await helper.chain.getLatestBlockNumber();721  const currentPeriodBlock = BigInt(relayBlockNumber) % LOCKING_PERIOD;722723  if (currentPeriodBlock > waitBlockLessThan) {724    await helper.wait.forRelayBlockNumber(BigInt(relayBlockNumber) + LOCKING_PERIOD - currentPeriodBlock);725  }726}
modifiedtests/src/approve.test.tsdiffbeforeafterboth
--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -104,7 +104,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -141,7 +141,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -184,7 +184,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -235,7 +235,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);
     });
   });
@@ -265,7 +265,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -320,7 +320,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -357,7 +357,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);
     });
   });
@@ -423,7 +423,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);
     });
   });
@@ -474,7 +474,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -495,7 +495,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
modifiedtests/src/burnItem.test.tsdiffbeforeafterboth
--- a/tests/src/burnItem.test.ts
+++ b/tests/src/burnItem.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
@@ -80,7 +80,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
@@ -123,7 +123,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/change-collection-owner.test.tsdiffbeforeafterboth
--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -23,7 +23,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
@@ -46,7 +46,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -108,7 +108,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
modifiedtests/src/check-event/burnItemEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/burnItemEvent.test.ts
+++ b/tests/src/check-event/burnItemEvent.test.ts
@@ -24,7 +24,7 @@
   let alice: IKeyringPair;
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/check-event/createCollectionEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/createCollectionEvent.test.ts
+++ b/tests/src/check-event/createCollectionEvent.test.ts
@@ -23,7 +23,7 @@
   let alice: IKeyringPair;
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/check-event/createItemEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/createItemEvent.test.ts
+++ b/tests/src/check-event/createItemEvent.test.ts
@@ -23,7 +23,7 @@
   let alice: IKeyringPair;
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/check-event/createMultipleItemsEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/createMultipleItemsEvent.test.ts
+++ b/tests/src/check-event/createMultipleItemsEvent.test.ts
@@ -23,7 +23,7 @@
   let alice: IKeyringPair;
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/check-event/destroyCollectionEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/destroyCollectionEvent.test.ts
+++ b/tests/src/check-event/destroyCollectionEvent.test.ts
@@ -23,7 +23,7 @@
   let alice: IKeyringPair;
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/check-event/transferEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/transferEvent.test.ts
+++ b/tests/src/check-event/transferEvent.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
     });
   });
modifiedtests/src/check-event/transferFromEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/transferFromEvent.test.ts
+++ b/tests/src/check-event/transferFromEvent.test.ts
@@ -24,7 +24,7 @@
   let bob: IKeyringPair;
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
     });
   });
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -37,7 +37,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie, zeroBalance] = await helper.arrange.createAccounts([100n, 100n, 100n, 0n], donor);
     });
   });
@@ -183,7 +183,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie, ownerZeroBalance, senderZeroBalance] = await helper.arrange.createAccounts([100n, 100n, 100n, 0n, 0n], donor);
     });
   });
modifiedtests/src/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -49,7 +49,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([100n], donor);
     });
   });
@@ -110,7 +110,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([100n], donor);
     });
   });
modifiedtests/src/createItem.test.tsdiffbeforeafterboth
--- a/tests/src/createItem.test.ts
+++ b/tests/src/createItem.test.ts
@@ -50,7 +50,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
@@ -179,7 +179,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -22,7 +22,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([100n], donor);
     });
   });
@@ -168,7 +168,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/createMultipleItemsEx.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItemsEx.test.ts
+++ b/tests/src/createMultipleItemsEx.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -268,7 +268,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
modifiedtests/src/creditFeesToTreasury.test.tsdiffbeforeafterboth
--- a/tests/src/creditFeesToTreasury.test.ts
+++ b/tests/src/creditFeesToTreasury.test.ts
@@ -50,7 +50,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -22,7 +22,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([100n], donor);
     });
   });
@@ -62,7 +62,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/enableDisableTransfer.test.tsdiffbeforeafterboth
--- a/tests/src/enableDisableTransfer.test.ts
+++ b/tests/src/enableDisableTransfer.test.ts
@@ -23,7 +23,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
@@ -62,7 +62,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
     });
   });
modifiedtests/src/eth/allowlist.test.tsdiffbeforeafterboth
--- a/tests/src/eth/allowlist.test.ts
+++ b/tests/src/eth/allowlist.test.ts
@@ -22,7 +22,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -70,7 +70,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -29,7 +29,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -82,7 +82,7 @@
 
   before(async () => {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       const [alice] = await helper.arrange.createAccounts([10n], donor);
       ({collectionId: collection} = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}));
       minter = helper.eth.createAccount();
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -32,7 +32,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -144,7 +144,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -272,7 +272,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -314,7 +314,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/collectionProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
 import {itEth, usingEthPlaygrounds, expect} from './util/playgrounds';
 import {IKeyringPair} from '@polkadot/types/types';
 
@@ -7,7 +23,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await _helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -24,7 +24,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -150,7 +150,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -26,7 +26,7 @@
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -152,7 +152,7 @@
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/crossTransfer.test.tsdiffbeforeafterboth
--- a/tests/src/eth/crossTransfer.test.ts
+++ b/tests/src/eth/crossTransfer.test.ts
@@ -26,7 +26,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
@@ -69,7 +69,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
modifiedtests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -85,7 +85,7 @@
   before(async function() {
     await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -225,7 +225,7 @@
   before(async function() {
     await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -23,7 +23,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
@@ -55,7 +55,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
@@ -224,7 +224,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
@@ -277,7 +277,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
modifiedtests/src/eth/helpersSmoke.test.tsdiffbeforeafterboth
--- a/tests/src/eth/helpersSmoke.test.ts
+++ b/tests/src/eth/helpersSmoke.test.ts
@@ -22,7 +22,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
   
modifiedtests/src/eth/migration.test.tsdiffbeforeafterboth
--- a/tests/src/eth/migration.test.ts
+++ b/tests/src/eth/migration.test.ts
@@ -22,7 +22,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      superuser = privateKey('//Alice');
+      superuser = await privateKey('//Alice');
     });
   });
   
modifiedtests/src/eth/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -21,7 +21,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -25,7 +25,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -75,7 +75,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -137,7 +137,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -318,7 +318,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -371,7 +371,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
@@ -496,7 +496,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
modifiedtests/src/eth/payable.test.tsdiffbeforeafterboth
--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -23,7 +23,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -68,7 +68,7 @@
     expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());
   });
 
-  itEth('Balance can be retrieved from evm contract', async({helper, privateKey}) => {
+  itEth('Balance can be retrieved from evm contract', async({helper}) => {
     const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();
     const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();
 
@@ -80,7 +80,7 @@
 
     await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});
 
-    const receiver = privateKey(`//Receiver${Date.now()}`);
+    const [receiver] = await helper.arrange.createAccounts([0n], donor);
 
     // First receive balance on eth balance of bob
     {
@@ -110,7 +110,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/proxy/fungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/fungibleProxy.test.ts
+++ b/tests/src/eth/proxy/fungibleProxy.test.ts
@@ -38,7 +38,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -77,7 +77,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -38,7 +38,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -95,7 +95,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -139,7 +139,7 @@
   });
 
   //TODO: CORE-302 add eth methods
-  itWeb3.skip('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {
+  itWeb3.skip('Can perform mintBulk()', async () => {
     /*
     const collection = await createCollectionExpectSuccess({
       mode: {type: 'NFT'},
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -25,7 +25,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -117,7 +117,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -329,7 +329,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -370,7 +370,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -28,7 +28,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([20n], donor);
     });
   });
@@ -72,7 +72,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
@@ -136,7 +136,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([50n], donor);
     });
   });
@@ -327,7 +327,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([50n], donor);
     });
   });
@@ -382,7 +382,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([50n], donor);
     });
   });
@@ -472,7 +472,7 @@
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/eth/tokenProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -1,94 +1,116 @@
-import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess} from '../util/helpers';
-import {cartesian, collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3} from './util/helpers';
-import nonFungibleAbi from './nonFungibleAbi.json';
-import {expect} from 'chai';
-import {executeTransaction} from '../substrate/substrate-api';
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {itEth, usingEthPlaygrounds, expect, cartesian} from './util/playgrounds';
+import {IKeyringPair} from '@polkadot/types/types';
 
 describe('EVM token properties', () => {
-  itWeb3('Can be reconfigured', async({web3, api, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+  let donor: IKeyringPair;
+  let alice: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+      [alice] = await helper.arrange.createAccounts([100n], donor);
+    });
+  });
+
+  itEth('Can be reconfigured', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
     for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {
-      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-      await addCollectionAdminExpectSuccess(alice, collection, {Ethereum: caller});
+      const collection = await helper.nft.mintCollection(alice);
+      await collection.addAdmin(alice, {Ethereum: caller});
       
-      const address = collectionIdToAddress(collection);
-      const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+      const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+      const contract = helper.ethNativeContract.collection(address, 'nft', caller);
   
       await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller});
   
-      const state = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();
-      expect(state).to.be.deep.equal({
-        [web3.utils.toHex('testKey')]: {mutable, collectionAdmin, tokenOwner},
-      });
+      expect(await collection.getPropertyPermissions()).to.be.deep.equal([{
+        key: 'testKey',
+        permission: {mutable, collectionAdmin, tokenOwner},
+      }]);
     }
   });
-  itWeb3('Can be set', async({web3, api, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-    const token = await createItemExpectSuccess(alice, collection, 'NFT');
 
-    await executeTransaction(api, alice, api.tx.unique.setTokenPropertyPermissions(collection, [{
+  itEth('Can be set', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const collection = await helper.nft.mintCollection(alice);
+    const token = await collection.mintToken(alice);
+
+    await collection.setTokenPropertyPermissions(alice, [{
       key: 'testKey',
       permission: {
         collectionAdmin: true,
       },
-    }]));
+    }]);
 
-    await addCollectionAdminExpectSuccess(alice, collection, {Ethereum: caller});
+    await collection.addAdmin(alice, {Ethereum: caller});
 
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft', caller);
 
-    await contract.methods.setProperty(token, 'testKey', Buffer.from('testValue')).send({from: caller});
+    await contract.methods.setProperty(token.tokenId, 'testKey', Buffer.from('testValue')).send({from: caller});
 
-    const [{value}] = (await api.rpc.unique.tokenProperties(collection, token, ['testKey'])).toHuman()! as any;
+    const [{value}] = await token.getProperties(['testKey']);
     expect(value).to.equal('testValue');
   });
-  itWeb3('Can be deleted', async({web3, api, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
-    const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
-    const token = await createItemExpectSuccess(alice, collection, 'NFT');
 
-    await executeTransaction(api, alice, api.tx.unique.setTokenPropertyPermissions(collection, [{
+  itEth('Can be deleted', async({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const collection = await helper.nft.mintCollection(alice);
+    const token = await collection.mintToken(alice);
+
+    await collection.setTokenPropertyPermissions(alice, [{
       key: 'testKey',
       permission: {
         mutable: true,
         collectionAdmin: true,
       },
-    }]));
-    await executeTransaction(api, alice, api.tx.unique.setTokenProperties(collection, token, [{key: 'testKey', value: 'testValue'}]));
+    }]);
+    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);
 
-    await addCollectionAdminExpectSuccess(alice, collection, {Ethereum: caller});
+    await collection.addAdmin(alice, {Ethereum: caller});
 
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft', caller);
 
-    await contract.methods.deleteProperty(token, 'testKey').send({from: caller});
+    await contract.methods.deleteProperty(token.tokenId, 'testKey').send({from: caller});
 
-    const result = (await api.rpc.unique.tokenProperties(collection, token, ['testKey'])).toJSON()! as any;
+    const result = await token.getProperties(['testKey']);
     expect(result.length).to.equal(0);
   });
-  itWeb3('Can be read', async({web3, api, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-    const caller = createEthAccount(web3);
-    const collection = await createCollectionExpectSuccess({mode: {type:'NFT'}});
-    const token = await createItemExpectSuccess(alice, collection, 'NFT');
 
-    await executeTransaction(api, alice, api.tx.unique.setTokenPropertyPermissions(collection, [{
+  itEth('Can be read', async({helper}) => {
+    const caller = helper.eth.createAccount();
+    const collection = await helper.nft.mintCollection(alice);
+    const token = await collection.mintToken(alice);
+
+    await collection.setTokenPropertyPermissions(alice, [{
       key: 'testKey',
       permission: {
         collectionAdmin: true,
       },
-    }]));
-    await executeTransaction(api, alice, api.tx.unique.setTokenProperties(collection, token, [{key: 'testKey', value: 'testValue'}]));
+    }]);
+    await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);
 
-    const address = collectionIdToAddress(collection);
-    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft', caller);
 
-    const value = await contract.methods.property(token, 'testKey').call();
-    expect(value).to.equal(web3.utils.toHex('testValue'));
+    const value = await contract.methods.property(token.tokenId, 'testKey').call();
+    expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));
   });
 });
modifiedtests/src/eth/util/playgrounds/index.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/index.ts
+++ b/tests/src/eth/util/playgrounds/index.ts
@@ -1,6 +1,7 @@
 // Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
+import * as path from 'path';
 import {IKeyringPair} from '@polkadot/types/types';
 
 import config from '../../../config';
@@ -13,12 +14,13 @@
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import chaiLike from 'chai-like';
-import {requirePalletsOrSkip} from '../../../util/playgrounds';
+import {getTestSeed, requirePalletsOrSkip} from '../../../util/playgrounds';
+
 chai.use(chaiAsPromised);
 chai.use(chaiLike);
 export const expect = chai.expect;
 
-export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
+export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair>) => Promise<void>) => {
   const silentConsole = new SilentConsole();
   silentConsole.enable();
 
@@ -28,7 +30,20 @@
     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);
+    const privateKey = async (seed: string | {filename: string}) => {
+      if (typeof seed === 'string') {
+        return helper.util.fromSeed(seed, ss58Format);
+      }
+      else {
+        const actualSeed = getTestSeed(seed.filename);
+        let account = helper.util.fromSeed(actualSeed, ss58Format);
+        if (await helper.balance.getSubstrate(account.address) == 0n) {
+          console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);
+          account = helper.util.fromSeed('//Alice', ss58Format);
+        }
+        return account;
+      }
+    };
     await code(helper, privateKey);
   }
   finally {
@@ -38,7 +53,7 @@
   }
 };
   
-export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
+export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
   (opts.only ? it.only : 
     opts.skip ? it.skip : it)(name, async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
@@ -51,13 +66,25 @@
   });
 }
 
-export async function itEthIfWithPallet(name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
+export async function itEthIfWithPallet(name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
   return itEth(name, cb, {requiredPallets: required, ...opts});
 }
 
-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});
+itEth.only = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair> }) => any) => itEth(name, cb, {only: true});
+itEth.skip = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair> }) => any) => itEth(name, cb, {skip: true});
 
-itEthIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEthIfWithPallet(name, required, cb, {only: true});
-itEthIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEthIfWithPallet(name, required, cb, {skip: true});
+itEthIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair> }) => any) => itEthIfWithPallet(name, required, cb, {only: true});
+itEthIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair> }) => any) => itEthIfWithPallet(name, required, cb, {skip: true});
 itEth.ifWithPallets = itEthIfWithPallet;
+
+type ElementOf<A> = A extends readonly (infer T)[] ? T : never;
+// I want a fancier api, not a memory efficiency
+export function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {
+  if(args.length === 0) {
+    yield internalRest as any;
+    return;
+  }
+  for(const value of args[0]) {
+    yield* cartesian([...internalRest, value], ...args.slice(1)) as any;
+  }
+}
modifiedtests/src/evmCoder.test.tsdiffbeforeafterboth
--- a/tests/src/evmCoder.test.ts
+++ b/tests/src/evmCoder.test.ts
@@ -92,7 +92,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
   
modifiedtests/src/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/fungible.test.ts
+++ b/tests/src/fungible.test.ts
@@ -20,12 +20,13 @@
 const U128_MAX = (1n << 128n) - 1n;
 
 describe('integration test: Fungible functionality:', () => {
+  let donor: IKeyringPair;
   let alice: IKeyringPair;
   let bob: IKeyringPair;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
     });
   });
@@ -43,9 +44,9 @@
     expect(aliceBalance).to.be.equal(U128_MAX);
   });
   
-  itSub('RPC method tokenOnewrs for fungible collection and token', async ({helper, privateKey}) => {
+  itSub('RPC method tokenOnewrs for fungible collection and token', async ({helper}) => {
     const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
-    const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address}));
+    const facelessCrowd = (await helper.arrange.createAccounts(Array(7).fill(0n), donor)).map(keyring => {return {Substrate: keyring.address};});
 
     const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
 
@@ -64,7 +65,7 @@
     expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
     expect(owners.length).to.be.equal(10);
     
-    const eleven = privateKey('//ALice+11');
+    const [eleven] = await helper.arrange.createAccounts([0n], donor);
     expect(await collection.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true;
     expect((await collection.getTop10Owners()).length).to.be.equal(10);
   });
modifiedtests/src/getPropertiesRpc.test.tsdiffbeforeafterboth
--- a/tests/src/getPropertiesRpc.test.ts
+++ b/tests/src/getPropertiesRpc.test.ts
@@ -63,7 +63,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (_, privateKey) => {
-      alice = privateKey('//Alice');
+      alice = await privateKey({filename: __filename});
     });
   });
 
modifiedtests/src/inflation.test.tsdiffbeforeafterboth
--- a/tests/src/inflation.test.ts
+++ b/tests/src/inflation.test.ts
@@ -23,7 +23,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (_, privateKey) => {
-      superuser = privateKey('//Alice');
+      superuser = await privateKey('//Alice');
     });
   });
   
modifiedtests/src/limits.test.tsdiffbeforeafterboth
--- a/tests/src/limits.test.ts
+++ b/tests/src/limits.test.ts
@@ -22,7 +22,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -60,7 +60,7 @@
     await usingPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
@@ -292,7 +292,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
@@ -333,7 +333,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
@@ -366,7 +366,7 @@
     await usingPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
@@ -404,7 +404,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/nesting/graphs.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/graphs.test.ts
+++ b/tests/src/nesting/graphs.test.ts
@@ -45,7 +45,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([10n], donor);
     });
   });
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -23,7 +23,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);
     });
   });
@@ -137,7 +137,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([50n, 10n, 10n], donor);
     });
   });
@@ -343,7 +343,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 50n], donor);
     });
   });
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -26,7 +26,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);
     });
   });
@@ -141,7 +141,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
     });
   });
@@ -269,7 +269,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
     });
   });
@@ -332,7 +332,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);
     });
   });
@@ -453,7 +453,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
 
@@ -777,7 +777,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       let dave: IKeyringPair;
       [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor);
 
@@ -977,7 +977,7 @@
     await usingPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
@@ -1002,7 +1002,7 @@
     ])).to.be.rejectedWith(/common\.NoPermission/);
   });
 
-  itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) =>  {
+  itSub('Forbids mutating token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) =>  {
     const token = await prepare(helper);
 
     await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}]))
modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -22,7 +22,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice] = await helper.arrange.createAccounts([50n], donor);
     });
   });
@@ -91,7 +91,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);
     });
   });
modifiedtests/src/nextSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/nextSponsoring.test.ts
+++ b/tests/src/nextSponsoring.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([20n, 10n], donor);
     });
   });
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -20,6 +20,7 @@
 const MAX_REFUNGIBLE_PIECES = 1_000_000_000_000_000_000_000n;
 
 describe('integration test: Refungible functionality:', async () => {
+  let donor: IKeyringPair;
   let alice: IKeyringPair;
   let bob: IKeyringPair;
 
@@ -27,7 +28,7 @@
     await usingPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
-      const donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
     });
   });
@@ -61,9 +62,9 @@
       .to.eventually.be.rejectedWith(/refungible\.WrongRefungiblePieces/);
   });
   
-  itSub('RPC method tokenOnewrs for refungible collection and token', async ({helper, privateKey}) => {
+  itSub('RPC method tokenOnewrs for refungible collection and token', async ({helper}) => {
     const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
-    const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address}));
+    const facelessCrowd = (await helper.arrange.createAccounts(Array(7).fill(0n), donor)).map(keyring => {return {Substrate: keyring.address};});
 
     const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
 
@@ -82,7 +83,7 @@
     expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
     expect(owners.length).to.be.equal(10);
     
-    const eleven = privateKey('//ALice+11');
+    const [eleven] = await helper.arrange.createAccounts([0n], donor);
     expect(await token.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true;
     expect((await token.getTop10Owners()).length).to.be.equal(10);
   });
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -23,7 +23,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([20n, 10n], donor);
     });
   });
@@ -62,7 +62,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([20n, 10n, 10n], donor);
     });
   });
modifiedtests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionSponsor.test.ts
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -24,7 +24,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
     });
   });
@@ -78,7 +78,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([20n, 10n, 10n], donor);
     });
   });
modifiedtests/src/rpc.test.tsdiffbeforeafterboth
--- a/tests/src/rpc.test.ts
+++ b/tests/src/rpc.test.ts
@@ -25,7 +25,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([20n, 10n], donor);
     });
   });
modifiedtests/src/setCollectionLimits.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -29,7 +29,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([20n, 10n], donor);
     });
   });
@@ -104,7 +104,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([20n, 10n], donor);
     });
   });
modifiedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -24,7 +24,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([20n, 10n, 10n], donor);
     });
   });
@@ -93,7 +93,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([10n, 5n], donor);
     });
   });
modifiedtests/src/setPermissions.test.tsdiffbeforeafterboth
--- a/tests/src/setPermissions.test.ts
+++ b/tests/src/setPermissions.test.ts
@@ -23,7 +23,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
     });
   });
@@ -79,7 +79,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
     });
   });
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -19,12 +19,13 @@
 import {itSub, Pallets, usingPlaygrounds, expect} from './util/playgrounds';
 
 describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {
+  let donor: IKeyringPair;
   let alice: IKeyringPair;
   let bob: IKeyringPair;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);
     });
   });
@@ -42,8 +43,7 @@
     expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;
   });
 
-  itSub('Inability to pay fees error message is correct', async ({helper, privateKey}) => {
-    const donor = privateKey('//Alice');
+  itSub('Inability to pay fees error message is correct', async ({helper}) => {
     const [zero] = await helper.arrange.createAccounts([0n], donor);
 
     // console.error = () => {};
@@ -117,7 +117,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);
     });
   });
@@ -256,7 +256,7 @@
 
   before(async function() {
     await usingEthPlaygrounds(async (_, privateKey) => {
-      donor = privateKey('//Alice');
+      donor = await privateKey({filename: __filename});
     });
   });
   
modifiedtests/src/transferFrom.test.tsdiffbeforeafterboth
--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -24,7 +24,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([20n, 10n, 10n], donor);
     });
   });
@@ -91,7 +91,7 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      const donor = privateKey('//Alice');
+      const donor = await privateKey({filename: __filename});
       [alice, bob, charlie] = await helper.arrange.createAccounts([50n, 10n, 10n], donor);
     });
   });
addedtests/src/util/playgrounds/feedAlices.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/util/playgrounds/feedAlices.ts
@@ -0,0 +1,77 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import * as path from 'path';
+import {promises as fs} from 'fs';
+import {usingPlaygrounds} from '.';
+
+async function getFiles(rootPath: string): Promise<string[]> {
+  const files = await fs.readdir(rootPath, {withFileTypes: true});
+  const filenames: string[] = [];
+  for (const entry of files) {
+    const res = path.resolve(rootPath, entry.name);
+    if (entry.isDirectory()) {
+      filenames.push(...await getFiles(res));
+    } else {
+      filenames.push(res);
+    }
+  }
+  return filenames;
+}
+
+const fundFilenames = async () => {
+  await usingPlaygrounds(async (helper, privateKey) => {
+    const oneToken = helper.balance.getOneTokenNominal();
+    const alice = await privateKey('//Alice');
+    const nonce = await helper.chain.getNonce(alice.address);
+    const filenames = await getFiles(path.resolve(__dirname, '../..'));
+
+    console.log(`Main Alice address: ${alice.address}, with balance:`, await helper.balance.getSubstrate(alice.address));
+
+    const batchSize = 20;
+    let balanceGrantedCounter = 0;
+    for (let b = 0; b < filenames.length; b += batchSize) {
+      const tx = [];
+      let batchBalanceGrantedCounter = 0;
+      for (const f of filenames.slice(b, b + batchSize)) {
+        if (!f.endsWith('.test.ts')) continue;
+        const account = await privateKey({filename: f});
+        const aliceBalance = await helper.balance.getSubstrate(account.address);
+
+        if (aliceBalance < 5000n * oneToken) {
+          tx.push(helper.executeExtrinsic(
+            alice, 
+            'api.tx.balances.transfer',
+            [account.address, 10000n * oneToken],
+            true,
+            {nonce: nonce + balanceGrantedCounter++},
+          ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;}));
+          batchBalanceGrantedCounter++;
+        }
+      }
+
+      if(tx.length > 0) {
+        console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`);
+        const result = await Promise.all(tx);
+        if (result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.');
+      }
+    }
+
+    if (balanceGrantedCounter == 0) console.log('No account needs additional funding.');
+  });
+};
+
+const fundFilenamesWithRetries = async (retriesLeft: number): Promise<boolean> => {
+  if (retriesLeft <= 0) return Promise.resolve(false);
+  return fundFilenames()
+    .then(() => Promise.resolve(true))
+    .catch(() => {
+      console.error(`Some transactions have failed. ${retriesLeft >= 1 ? 'Retrying...' : 'Something is wrong.'}`);
+      return fundFilenamesWithRetries(--retriesLeft);
+    });
+};
+
+fundFilenamesWithRetries(2).then((result) => process.exit(result ? 0 : 1)).catch(e => {
+  console.error(e);
+  process.exit(1);
+});
modifiedtests/src/util/playgrounds/index.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/index.ts
+++ b/tests/src/util/playgrounds/index.ts
@@ -1,6 +1,8 @@
 // Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
 // SPDX-License-Identifier: Apache-2.0
 
+import * as path from 'path';
+import * as crypto from 'crypto';
 import {IKeyringPair} from '@polkadot/types/types';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
@@ -12,7 +14,16 @@
 chai.use(chaiAsPromised);
 export const expect = chai.expect;
 
-export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>, url: string = config.substrateUrl) => {
+const getTestHash = (filename: string) => {
+  return crypto.createHash('md5').update(path.basename(filename)).digest('hex');
+};
+
+export const getTestSeed = (filename: string) => {
+  return `//Alice+${getTestHash(filename)}`;
+};
+
+// todo:playgrounds normalize to seed and filename
+export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise<IKeyringPair>) => Promise<void>, url: string = config.substrateUrl) => {
   const silentConsole = new SilentConsole();
   silentConsole.enable();
 
@@ -21,7 +32,20 @@
   try {
     await helper.connect(url);
     const ss58Format = helper.chain.getChainProperties().ss58Format;
-    const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format);
+    const privateKey = async (seed: string | {filename: string}) => {
+      if (typeof seed === 'string') {
+        return helper.util.fromSeed(seed, ss58Format);
+      }
+      else {
+        const actualSeed = getTestSeed(seed.filename);
+        let account = helper.util.fromSeed(actualSeed, ss58Format);
+        if (await helper.balance.getSubstrate(account.address) == 0n) {
+          console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);
+          account = helper.util.fromSeed('//Alice', ss58Format);
+        }
+        return account;
+      }
+    };
     await code(helper, privateKey);
   }
   finally {
@@ -50,7 +74,7 @@
   }
 }
 
-export async function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
+export async function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
   (opts.only ? it.only : 
     opts.skip ? it.skip : it)(name, async function () {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -62,12 +86,12 @@
     });
   });
 }
-export async function itSubIfWithPallet(name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
+export async function itSubIfWithPallet(name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
   return itSub(name, cb, {requiredPallets: required, ...opts});
 }
-itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {only: true});
-itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {skip: true});
+itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {only: true});
+itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {skip: true});
 
-itSubIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSubIfWithPallet(name, required, cb, {only: true});
-itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});
+itSubIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {only: true});
+itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});
 itSub.ifWithPallets = itSubIfWithPallet;