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

difftreelog

Merge pull request #887 from UniqueNetwork/fix/staking-events

Yaroslav Bolyukin2023-02-16parents: #58315dd #5ae113b.patch.diff
in: master

4 files changed

modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -793,7 +793,7 @@
 
 		<PendingUnstake<T>>::insert(unpending_block, pendings);
 
-		Self::deposit_event(Event::Unstake(staker_id, total_staked));
+		Self::deposit_event(Event::Unstake(staker_id, unstaked_balance));
 
 		Ok(())
 	}
modifiedtests/src/benchmarks/utils/common.tsdiffbeforeafterboth
before · tests/src/benchmarks/utils/common.ts
1import {EthUniqueHelper} from '../../eth/util';2import {ITokenPropertyPermission, TCollectionMode} from '../../util/playgrounds/types';3import {UniqueNFTCollection, UniqueRFTCollection} from '../../util/playgrounds/unique';4import {IKeyringPair} from '@polkadot/types/types';5import {ContractImports} from '../../eth/util/playgrounds/types';67export const PROPERTIES = Array(40)8  .fill(0)9  .map((_, i) => {10    return {11      key: `key_${i}`,12      value: Uint8Array.from(Buffer.from(`value_${i}`)),13    };14  });1516export const SUBS_PROPERTIES = Array(40)17  .fill(0)18  .map((_, i) => {19    return {20      key: `key_${i}`,21      value: `value_${i}`,22    };23  });2425export const PERMISSIONS: ITokenPropertyPermission[] = PROPERTIES.map((p) => {26  return {27    key: p.key,28    permission: {29      tokenOwner: true,30      collectionAdmin: true,31      mutable: true,32    },33  };34});3536export function convertToTokens(value: bigint, nominal = 1000_000_000_000_000_000n): number {37  return Number((value * 1000n) / nominal) / 1000;38}3940export async function createCollectionForBenchmarks(41  mode : TCollectionMode,42  helper: EthUniqueHelper,43  privateKey: (seed: string) => Promise<IKeyringPair>,44  ethSigner: string,45  proxyContract: string | null,46  permissions: ITokenPropertyPermission[],47) {48  const donor = await privateKey('//Alice');4950  const collection = await helper[mode].mintCollection(donor, {51    name: 'test mintToSubstrate',52    description: 'EVMHelpers',53    tokenPrefix: mode,54    ...(mode != 'ft' ? {55      tokenPropertyPermissions: [56        {57          key: 'url',58          permission: {59            tokenOwner: true,60            collectionAdmin: true,61            mutable: true,62          },63        },64        {65          key: 'URI',66          permission: {67            tokenOwner: true,68            collectionAdmin: true,69            mutable: true,70          },71        },72      ],73    } : {}),74    limits: {sponsorTransferTimeout: 0, sponsorApproveTimeout: 0},75    permissions: {mintMode: true},76  });7778  await collection.addToAllowList(donor, {79    Ethereum: helper.address.substrateToEth(donor.address),80  });81  await collection.addToAllowList(donor, {Substrate: donor.address});82  await collection.addAdmin(donor, {Ethereum: ethSigner});83  await collection.addAdmin(donor, {84    Ethereum: helper.address.substrateToEth(donor.address),85  });8687  if (proxyContract) {88    await collection.addToAllowList(donor, {Ethereum: proxyContract});89    await collection.addAdmin(donor, {Ethereum: proxyContract});90  }91  if (collection instanceof UniqueNFTCollection || collection instanceof UniqueRFTCollection)92    await collection.setTokenPropertyPermissions(donor, permissions);9394  return collection;95}
after · tests/src/benchmarks/utils/common.ts
1import {EthUniqueHelper} from '../../eth/util';2import {ITokenPropertyPermission, TCollectionMode} from '../../util/playgrounds/types';3import {UniqueNFTCollection, UniqueRFTCollection} from '../../util/playgrounds/unique';4import {IKeyringPair} from '@polkadot/types/types';56export const PROPERTIES = Array(40)7  .fill(0)8  .map((_, i) => {9    return {10      key: `key_${i}`,11      value: Uint8Array.from(Buffer.from(`value_${i}`)),12    };13  });1415export const SUBS_PROPERTIES = Array(40)16  .fill(0)17  .map((_, i) => {18    return {19      key: `key_${i}`,20      value: `value_${i}`,21    };22  });2324export const PERMISSIONS: ITokenPropertyPermission[] = PROPERTIES.map((p) => {25  return {26    key: p.key,27    permission: {28      tokenOwner: true,29      collectionAdmin: true,30      mutable: true,31    },32  };33});3435export function convertToTokens(value: bigint, nominal = 1000_000_000_000_000_000n): number {36  return Number((value * 1000n) / nominal) / 1000;37}3839export async function createCollectionForBenchmarks(40  mode : TCollectionMode,41  helper: EthUniqueHelper,42  privateKey: (seed: string) => Promise<IKeyringPair>,43  ethSigner: string,44  proxyContract: string | null,45  permissions: ITokenPropertyPermission[],46) {47  const donor = await privateKey('//Alice');4849  const collection = await helper[mode].mintCollection(donor, {50    name: 'test mintToSubstrate',51    description: 'EVMHelpers',52    tokenPrefix: mode,53    ...(mode != 'ft' ? {54      tokenPropertyPermissions: [55        {56          key: 'url',57          permission: {58            tokenOwner: true,59            collectionAdmin: true,60            mutable: true,61          },62        },63        {64          key: 'URI',65          permission: {66            tokenOwner: true,67            collectionAdmin: true,68            mutable: true,69          },70        },71      ],72    } : {}),73    limits: {sponsorTransferTimeout: 0, sponsorApproveTimeout: 0},74    permissions: {mintMode: true},75  });7677  await collection.addToAllowList(donor, {78    Ethereum: helper.address.substrateToEth(donor.address),79  });80  await collection.addToAllowList(donor, {Substrate: donor.address});81  await collection.addAdmin(donor, {Ethereum: ethSigner});82  await collection.addAdmin(donor, {83    Ethereum: helper.address.substrateToEth(donor.address),84  });8586  if (proxyContract) {87    await collection.addToAllowList(donor, {Ethereum: proxyContract});88    await collection.addAdmin(donor, {Ethereum: proxyContract});89  }90  if (collection instanceof UniqueNFTCollection || collection instanceof UniqueRFTCollection)91    await collection.setTokenPropertyPermissions(donor, permissions);9293  return collection;94}
modifiedtests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth
--- a/tests/src/sub/appPromotion/appPromotion.test.ts
+++ b/tests/src/sub/appPromotion/appPromotion.test.ts
@@ -795,7 +795,7 @@
       const [_stake1, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
       await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));
 
-      const payoutToStaker = (await helper.admin.payoutStakers(palletAdmin, 100)).find((payout) => payout.staker === staker.address)?.payout;
+      const payoutToStaker = (await helper.admin.payoutStakers(palletAdmin, 100)).find((payout) => payout.staker === staker.address)!.payout;
       expect(payoutToStaker + 300n * nominal).to.equal(calculateIncome(300n * nominal));
 
       const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
@@ -867,12 +867,11 @@
       await helper.staking.stake(staker, 100n * nominal);
       await helper.staking.unstakePartial(staker, 100n * nominal - 1n);
 
-      const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
-      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));
+      const [_stake1, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));
 
-      const payouts = await helper.admin.payoutStakers(palletAdmin, 100);
-      const stakerPayout = payouts.find(p => p.staker === staker.address);
-      expect(stakerPayout!.stake).to.eq(100n * nominal + 1n);
+      const stakerPayout = await payUntilRewardFor(staker.address, helper);
+      expect(stakerPayout.stake).to.eq(100n * nominal + 1n);
     });
 
     itSub('can eventually pay all rewards', async ({helper}) => {
@@ -900,9 +899,73 @@
       } while (payouts.length !== 0);
     });
   });
+
+  describe('events', () => {
+    [
+      {method: 'unstakePartial' as const},
+      {method: 'unstakeAll' as const},
+    ].map(testCase => {
+      itSub(testCase.method, async ({helper}) => {
+        const unstakeParams = testCase.method === 'unstakePartial'
+          ? [100n * nominal - 1n]
+          : [];
+        const [staker] = getAccount(1);
+        await helper.staking.stake(staker, 100n * nominal);
+        await helper.staking.stake(staker, 200n * nominal);
+        const {result} = await helper.executeExtrinsic(staker, `api.tx.appPromotion.${testCase.method}`, unstakeParams);
+
+        const event = result.events.find(e => e.event.section === 'appPromotion' && e.event.method === 'Unstake');
+        const unstakerEvents = event?.event.data[0].toString();
+        const unstakedEvents = BigInt(event?.event.data[1].toString());
+        expect(unstakerEvents).to.eq(staker.address);
+        expect(unstakedEvents).to.eq(testCase.method === 'unstakeAll' ? 300n * nominal : 100n * nominal - 1n);
+      });
+    });
+
+    itSub('stake', async ({helper}) => {
+      const [staker] = getAccount(1);
+      const {result} = await helper.executeExtrinsic(staker, 'api.tx.appPromotion.stake', [100n * nominal]);
+
+      const event = result.events.find(e => e.event.section === 'appPromotion' && e.event.method === 'Stake');
+      const stakerEvents = event?.event.data[0].toString();
+      const stakedEvents = BigInt(event?.event.data[1].toString());
+      expect(stakerEvents).to.eq(staker.address);
+      expect(stakedEvents).to.eq(100n * nominal);
+    });
+
+    // Flaky
+    itSub.skip('payoutStakers', async ({helper}) => {
+      const [staker1, staker2] = getAccount(2);
+      const STAKE1 = 100n * nominal;
+      const STAKE2 = 200n * nominal;
+      await helper.staking.stake(staker1, STAKE1);
+      await helper.staking.stake(staker2, STAKE2);
+
+      const [stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker2.address});
+      await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));
+
+      const results = await helper.admin.payoutStakers(palletAdmin, 100);
+      const stakersEvents = results.filter(ev => ev.staker === staker1.address || ev.staker === staker2.address);
+      expect(stakersEvents).has.length(2);
+      expect(stakersEvents).has.not.ordered.members([
+        {staker: staker1.address, stake: STAKE1, payout: calculateIncome(STAKE1) - STAKE1},
+        {staker: staker2.address, stake: STAKE2, payout: calculateIncome(STAKE2) - STAKE2},
+      ]);
+    });
+  });
 });
 
 
+// Sometimes is is required to make a cycle in order for the payment to be calculated for a specific account
+async function payUntilRewardFor(account: string, helper: DevUniqueHelper) {
+  for (let i = 0; i < 3; i++) {
+    const payouts = await helper.admin.payoutStakers(palletAdmin, 100);
+    const accountPayout = payouts.find(p => p.staker === account);
+    if (accountPayout) return accountPayout;
+  }
+  throw Error(`Cannot find payout for ${account}`);
+}
+
 function calculateIncome(base: bigint, iter = 0, calcPeriod: bigint = UNLOCKING_PERIOD): bigint {
   const DAY = 7200n;
   const ACCURACY = 1_000_000_000n;
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -733,7 +733,7 @@
     this.helper = helper;
   }
 
-  async payoutStakers(signer: IKeyringPair, stakersToPayout: number) {
+  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {
     const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);
     return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {
       return {