git.delta.rocks / unique-network / refs/commits / 6a055472d116

difftreelog

Tests: unstakeAll after each test

Max Andreev2023-02-15parent: #5495238.patch.diff
in: master

2 files changed

modifiedtests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth
--- a/tests/src/sub/appPromotion/appPromotion.test.ts
+++ b/tests/src/sub/appPromotion/appPromotion.test.ts
@@ -26,6 +26,13 @@
 let nominal: bigint;
 let palletAddress: string;
 let accounts: IKeyringPair[];
+let usedAccounts: IKeyringPair[] = [];
+
+function getAccount(accountsNumber: number) {
+  const accs = accounts.splice(0, accountsNumber);
+  usedAccounts.push(...accs);
+  return accs;
+}
 // App promotion periods:
 // LOCKING_PERIOD = 12 blocks of relay
 // UNLOCKING_PERIOD = 6 blocks of parachain
@@ -39,15 +46,29 @@
       palletAdmin = await privateKey('//PromotionAdmin');
       nominal = helper.balance.getOneTokenNominal();
 
-      const accountBalances = new Array(100);
-      accountBalances.fill(1000n);
+      const accountBalances = new Array(200).fill(1000n);
       accounts = await helper.arrange.createAccounts(accountBalances, donor); // create accounts-pool to speed up tests
     });
   });
 
+  afterEach(async () => {
+    await usingPlaygrounds(async (helper) => {
+      let unstakeTxs = [];
+      for (const account of usedAccounts) {
+        if (unstakeTxs.length === 3) {
+          await Promise.all(unstakeTxs);
+          unstakeTxs = [];
+        }
+        unstakeTxs.push(helper.staking.unstakeAll(account));
+      }
+      await Promise.all(unstakeTxs);
+      usedAccounts = [];
+    });
+  });
+
   describe('stake extrinsic', () => {
     itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {
-      const [staker, recepient] = [accounts.pop()!, accounts.pop()!];
+      const [staker, recepient] = getAccount(2);
       const totalStakedBefore = await helper.staking.getTotalStaked();
 
       // Minimum stake amount is 100:
@@ -114,7 +135,7 @@
     });
 
     itSub('should allow to stake() if balance is locked with different id', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
 
       // staker has tokens locked with vesting id:
       await helper.balance.vestedTransfer(donor, staker.address, {start: 0n, period: 1n, periodCount: 1n, perPeriod: 200n * nominal});
@@ -147,7 +168,7 @@
     });
 
     itSub('should not allow to stake(), if stake amount is more than total free balance minus locked by staking', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
 
       // Can't stake full balance because Alice needs to pay some fee
       await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected; // With('Arithmetic')
@@ -159,7 +180,7 @@
     });
 
     itSub('for different accounts in one block is possible', async ({helper}) => {
-      const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];
+      const crowd = getAccount(4);
 
       const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));
       await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;
@@ -175,7 +196,7 @@
       {method: 'unstakePartial' as const},
     ].map(testCase => {
       itSub(`[${testCase.method}] should move tokens to "pendingUnstake" and subtract it from totalStaked`, async ({helper}) => {
-        const [staker, recepient] = [accounts.pop()!, accounts.pop()!];
+        const [staker, recepient] = getAccount(2);
         const totalStakedBefore = await helper.staking.getTotalStaked();
         const STAKE_AMOUNT = 900n * nominal;
 
@@ -201,7 +222,7 @@
       {method: 'unstakePartial' as const},
     ].map(testCase => {
       itSub(`[${testCase.method}] should unlock balance after unlocking period ends and remove it from "pendingUnstake"`, async ({helper}) => {
-        const staker = accounts.pop()!;
+        const [staker] = getAccount(1);
         await helper.staking.stake(staker, 100n * nominal);
         testCase.method === 'unstakeAll'
           ? await helper.staking.unstakeAll(staker)
@@ -224,7 +245,7 @@
       {method: 'unstakePartial' as const},
     ].map(testCase => {
       itSub(`[${testCase.method}] should successfully unstake multiple stakes`, async ({helper}) => {
-        const staker = accounts.pop()!;
+        const [staker] = getAccount(1);
         await helper.staking.stake(staker, 100n * nominal);
         await helper.staking.stake(staker, 200n * nominal);
         await helper.staking.stake(staker, 300n * nominal);
@@ -264,7 +285,7 @@
       {method: 'unstakePartial' as const},
     ].map(testCase => {
       itSub(`[${testCase.method}] should not have any effects if no active stakes`, async ({helper}) => {
-        const staker = accounts.pop()!;
+        const [staker] = getAccount(1);
 
         // unstake has no effect if no stakes at all
         testCase.method === 'unstakeAll'
@@ -298,7 +319,7 @@
       {method: 'unstakePartial' as const},
     ].map(testCase => {
       itSub(`[${testCase.method}] should create different pending-unlock for each unlocking stake`, async ({helper}) => {
-        const staker = accounts.pop()!;
+        const [staker] = getAccount(1);
         await helper.staking.stake(staker, 100n * nominal);
         testCase.method === 'unstakeAll'
           ? await helper.staking.unstakeAll(staker)
@@ -321,7 +342,7 @@
       {method: 'unstakePartial' as const},
     ].map(testCase => {
       itSub(`[${testCase.method}] should be possible for 3 accounts in one block`, async ({helper}) => {
-        const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
+        const stakers = getAccount(3);
 
         await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
         await Promise.all(stakers.map(staker => {
@@ -339,7 +360,7 @@
 
     itSub('should not be possible for more than 3 accounts in one block', async ({helper}) => {
       if (!await helper.arrange.isDevNode()) {
-        const stakers = await helper.arrange.createAccounts([200n,200n,200n,200n,200n,200n,200n,200n,200n,200n], donor);
+        const stakers = getAccount(10);
 
         await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
         const unstakingResults = await Promise.allSettled(stakers.map((staker, i) => {
@@ -354,7 +375,7 @@
     });
 
     itSub('Cannot partially unstake more than staked', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
       // Staker stakes 300:
       await helper.staking.stake(staker, 100n * nominal);
       await helper.staking.stake(staker, 200n * nominal);
@@ -374,7 +395,7 @@
     });
 
     itSub('Can partially unstake arbitrary amount', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
       await helper.staking.stake(staker, 100n * nominal);
       await helper.staking.stake(staker, 200n * nominal);
 
@@ -408,7 +429,7 @@
     });
 
     itSub('can mix different type of unstakes', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
       await helper.staking.stake(staker, 100n * nominal);
       await helper.staking.stake(staker, 200n * nominal);
 
@@ -433,7 +454,7 @@
   describe('collection sponsoring', () => {
     itSub('should actually sponsor transactions', async ({helper}) => {
       const api = helper.getApi();
-      const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
+      const [collectionOwner, tokenSender, receiver] = getAccount(3);
       const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});
       const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});
       await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId));
@@ -450,7 +471,7 @@
 
     itSub('can not be set by non admin', async ({helper}) => {
       const api = helper.getApi();
-      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];
+      const [collectionOwner, nonAdmin] = getAccount(2);
 
       const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
 
@@ -460,7 +481,7 @@
 
     itSub('should set pallet address as confirmed admin', async ({helper}) => {
       const api = helper.getApi();
-      const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];
+      const [collectionOwner, oldSponsor] = getAccount(2);
 
       // Can set sponsoring for collection without sponsor
       const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});
@@ -482,7 +503,7 @@
 
     itSub('can be overwritten by collection owner', async ({helper}) => {
       const api = helper.getApi();
-      const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];
+      const [collectionOwner, newSponsor] = getAccount(2);
       const collection  = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
       const collectionId = collection.collectionId;
 
@@ -501,7 +522,7 @@
     itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {
       const api = helper.getApi();
       const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};
-      const collectionWithLimits = await helper.nft.mintCollection(accounts.pop()!, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});
+      const collectionWithLimits = await helper.nft.mintCollection(getAccount(1)[0], {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});
 
       await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;
       expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);
@@ -509,7 +530,7 @@
 
     itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {
       const api = helper.getApi();
-      const collectionOwner = accounts.pop()!;
+      const [collectionOwner] = getAccount(1);
 
       // collection has never existed
       await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;
@@ -524,7 +545,7 @@
   describe('stopSponsoringCollection', () => {
     itSub('can not be called by non-admin', async ({helper}) => {
       const api = helper.getApi();
-      const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];
+      const [collectionOwner, nonAdmin] = getAccount(2);
       const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
 
       await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;
@@ -535,7 +556,7 @@
 
     itSub('should set sponsoring as disabled', async ({helper}) => {
       const api = helper.getApi();
-      const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];
+      const [collectionOwner, recepient] = getAccount(2);
       const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});
       const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});
 
@@ -553,7 +574,7 @@
 
     itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {
       const api = helper.getApi();
-      const collectionOwner = accounts.pop()!;
+      const [collectionOwner] = getAccount(1);
       const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});
       await collection.confirmSponsorship(collectionOwner);
 
@@ -563,7 +584,7 @@
     });
 
     itSub('should reject transaction if collection does not exist', async ({helper}) => {
-      const collectionOwner = accounts.pop()!;
+      const [collectionOwner] = getAccount(1);
       const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});
 
       await collection.burn(collectionOwner);
@@ -637,7 +658,7 @@
     });
 
     itEth('can not be set by non admin', async ({helper}) => {
-      const nonAdmin = accounts.pop()!;
+      const [nonAdmin] = getAccount(1);
       const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();
       const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);
       const contractHelper = await helper.ethNativeContract.contractHelpers(contractOwner);
@@ -719,7 +740,7 @@
     });
 
     itEth('can not be called by non-admin', async ({helper}) => {
-      const nonAdmin = accounts.pop()!;
+      const [nonAdmin] = getAccount(1);
       const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();
       const flipper = await helper.eth.deployFlipper(contractOwner);
 
@@ -729,7 +750,7 @@
     });
 
     itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {
-      const nonAdmin = accounts.pop()!;
+      const [nonAdmin] = getAccount(1);
       const contractOwner = (await helper.eth.createAccountWithBalance(donor, 1000n)).toLowerCase();
       const flipper = await helper.eth.deployFlipper(contractOwner);
       const contractHelper = await helper.ethNativeContract.contractHelpers(contractOwner);
@@ -741,12 +762,12 @@
 
   describe('payoutStakers', () => {
     itSub('can not be called by non admin', async ({helper}) => {
-      const nonAdmin = accounts.pop()!;
+      const [nonAdmin] = getAccount(1);
       await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');
     });
 
     itSub('should increase total staked', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
       const totalStakedBefore = await helper.staking.getTotalStaked();
       await helper.staking.stake(staker, 100n * nominal);
 
@@ -763,7 +784,7 @@
     });
 
     itSub('should credit 0.05% for staking period', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
 
       await waitPromotionPeriodDoesntEnd(helper);
 
@@ -789,7 +810,7 @@
     });
 
     itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
 
       await helper.staking.stake(staker, 100n * nominal);
       // wait for two rewards are available:
@@ -808,7 +829,7 @@
 
     itSub('should not be credited for pending-unstaked tokens', async ({helper}) => {
       // staker unstakes before rewards been payed
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
       await helper.staking.stake(staker, 100n * nominal);
       const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
       await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);
@@ -823,7 +844,7 @@
     });
 
     itSub('should bring compound interest', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
 
       await helper.staking.stake(staker, 100n * nominal);
 
@@ -841,7 +862,7 @@
     });
 
     itSub('can calculate reward for tiny stake', async ({helper}) => {
-      const staker = accounts.pop()!;
+      const [staker] = getAccount(1);
       await helper.staking.stake(staker, 100n * nominal);
       await helper.staking.stake(staker, 100n * nominal);
       await helper.staking.unstakePartial(staker, 100n * nominal - 1n);
@@ -855,7 +876,8 @@
     });
 
     itSub('can eventually pay all rewards', async ({helper}) => {
-      const stakers = await helper.arrange.createAccounts(new Array(100).fill(200n), donor);
+      const stakers = getAccount(30);
+      // Create 30 stakes:
       await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
 
       let unstakingTxs = [];
@@ -867,7 +889,7 @@
         unstakingTxs.push(helper.staking.unstakePartial(staker, 100n * nominal - 1n));
       }
 
-      const [staker] = await helper.arrange.createAccounts([1000n], donor);
+      const [staker] = getAccount(1);
       await helper.staking.stake(staker, 100n * nominal);
       const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
       await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));
modifiedtests/src/util/globalSetup.tsdiffbeforeafterboth
before · tests/src/util/globalSetup.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {5  usingPlaygrounds, Pallets, DONOR_FUNDING, MINIMUM_DONOR_FUND, LOCKING_PERIOD, UNLOCKING_PERIOD,6} from './index';7import * as path from 'path';8import {promises as fs} from 'fs';910// This function should be called before running test suites.11const globalSetup = async (): Promise<void> => {12  await usingPlaygrounds(async (helper, privateKey) => {13    try {14      // 1. Wait node producing blocks15      await helper.wait.newBlocks(1, 600_000);1617      // 2. Create donors for test files18      await fundFilenamesWithRetries(3)19        .then((result) => {20          if (!result) throw Error('Some problems with fundFilenamesWithRetries');21        });2223      // 3. Configure App Promotion24      const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]);25      if (missingPallets.length === 0) {26        const superuser = await privateKey('//Alice');27        const palletAddress = helper.arrange.calculatePalletAddress('appstake');28        const palletAdmin = await privateKey('//PromotionAdmin');29        const api = helper.getApi();30        await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));31        const nominal = helper.balance.getOneTokenNominal();32        await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 1000n * nominal);33        await helper.balance.transferToSubstrate(superuser, palletAddress, 1000n * nominal);34        await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration35          .setAppPromotionConfigurationOverride({36            recalculationInterval: LOCKING_PERIOD,37            pendingInterval: UNLOCKING_PERIOD})], true);38      }39    } catch (error) {40      console.error(error);41      throw Error('Error during globalSetup');42    }43  });44};4546async function getFiles(rootPath: string): Promise<string[]> {47  const files = await fs.readdir(rootPath, {withFileTypes: true});48  const filenames: string[] = [];49  for (const entry of files) {50    const res = path.resolve(rootPath, entry.name);51    if (entry.isDirectory()) {52      filenames.push(...await getFiles(res));53    } else {54      filenames.push(res);55    }56  }57  return filenames;58}5960const fundFilenames = async () => {61  await usingPlaygrounds(async (helper, privateKey) => {62    const oneToken = helper.balance.getOneTokenNominal();63    const alice = await privateKey('//Alice');64    const nonce = await helper.chain.getNonce(alice.address);65    const filenames = await getFiles(path.resolve(__dirname, '..'));6667    // batching is actually undesireable, it takes away the time while all the transactions actually succeed68    const batchSize = 300;69    let balanceGrantedCounter = 0;70    for (let b = 0; b < filenames.length; b += batchSize) {71      const tx = [];72      let batchBalanceGrantedCounter = 0;73      for (let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) {74        const f = filenames[b + i];75        if (!f.endsWith('.test.ts') && !f.endsWith('seqtest.ts') || f.includes('.outdated')) continue;76        const account = await privateKey({filename: f, ignoreFundsPresence: true});77        const aliceBalance = await helper.balance.getSubstrate(account.address);7879        if (aliceBalance < MINIMUM_DONOR_FUND * oneToken) {80          tx.push(helper.executeExtrinsic(81            alice,82            'api.tx.balances.transfer',83            [account.address, DONOR_FUNDING * oneToken],84            true,85            {nonce: nonce + balanceGrantedCounter++},86          ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;}));87          batchBalanceGrantedCounter++;88        }89      }9091      if(tx.length > 0) {92        console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`);93        const result = await Promise.all(tx);94        if (result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.');95      }96    }9798    if (balanceGrantedCounter == 0) console.log('No account needs additional funding.');99  });100};101102const fundFilenamesWithRetries = (retriesLeft: number): Promise<boolean> => {103  if (retriesLeft <= 0) return Promise.resolve(false);104  return fundFilenames()105    .then(() => Promise.resolve(true))106    .catch(e => {107      console.error(e);108      console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`);109      return fundFilenamesWithRetries(--retriesLeft);110    });111};112113globalSetup().catch(() => process.exit(1));