difftreelog
Merge pull request #887 from UniqueNetwork/fix/staking-events
in: master
4 files changed
pallets/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(())
}
tests/src/benchmarks/utils/common.tsdiffbeforeafterboth1import {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}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}tests/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;
tests/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 {