Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: incorporate era length which may be less than a day for some chains while calc a validators APY #1659

Merged
merged 5 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 27 additions & 23 deletions packages/extension-polkagate/src/hooks/useBlockInterval.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

import type { ApiPromise } from '@polkadot/api';

Expand All @@ -17,29 +16,34 @@ export const A_DAY = new BN(24 * 60 * 60 * 1000);
const THRESHOLD = BN_THOUSAND.div(BN_TWO);
const DEFAULT_TIME = new BN(6_000);

function calcInterval(api: ApiPromise): BN {
return bnMin(A_DAY, (
// Babe, e.g. Relay chains (Substrate defaults)
api.consts.babe?.expectedBlockTime ||
// POW, eg. Kulupu
api.consts.difficulty?.targetBlockTime ||
// Subspace
api.consts.subspace?.expectedBlockTime || (
// Check against threshold to determine value validity
api.consts.timestamp?.minimumPeriod.gte(THRESHOLD)
// Default minimum period config
? api.consts.timestamp.minimumPeriod.mul(BN_TWO)
: api.query.parachainSystem
// default guess for a parachain
? DEFAULT_TIME.mul(BN_TWO)
// default guess for others
: DEFAULT_TIME
)
));
export function calcInterval (api: ApiPromise | undefined): BN {
if (!api) {
return DEFAULT_TIME;
}

return bnMin(A_DAY, (
// Babe, e.g. Relay chains (Substrate defaults)
api.consts['babe']?.['expectedBlockTime'] as unknown as BN ||
// POW, eg. Kulupu
api.consts['difficulty']?.['targetBlockTime'] as unknown as BN ||
// Subspace
// Subspace
api.consts['subspace']?.['expectedBlockTime'] || (
// Check against threshold to determine value validity
(api.consts['timestamp']?.['minimumPeriod'] as unknown as BN).gte(THRESHOLD)
// Default minimum period config
? (api.consts['timestamp']['minimumPeriod'] as unknown as BN).mul(BN_TWO)
: api.query['parachainSystem']
// default guess for a parachain
? DEFAULT_TIME.mul(BN_TWO)
// default guess for others
: DEFAULT_TIME
)
));
}

export default function useBlockInterval(address: string | undefined): BN | undefined {
const api = useApi(address);
export default function useBlockInterval (address: string | undefined): BN | undefined {
const api = useApi(address);

return useMemo(() => api && calcInterval(api), [api]);
return useMemo(() => api && calcInterval(api), [api]);
}
31 changes: 18 additions & 13 deletions packages/extension-polkagate/src/hooks/useValidatorApy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,46 @@

import type { ApiPromise } from '@polkadot/api';
import type { Option, u128 } from '@polkadot/types';
//@ts-ignore
// @ts-ignore
import type { PalletStakingActiveEraInfo, PalletStakingEraRewardPoints, PalletStakingValidatorPrefs, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup';

import { useCallback, useEffect, useState } from 'react';

import { BN, BN_HUNDRED, BN_ZERO } from '@polkadot/util';
import { BN, BN_ZERO } from '@polkadot/util';

import { calcInterval } from './useBlockInterval';

interface ValidatorEraInfo {
netReward: BN;
netReward: number;
total: BN;
}

export default function useValidatorApy (api: ApiPromise | undefined, validatorAddress: string, isElected?: boolean): string | undefined | null {
const [apy, setApy] = useState<string | null>();
const blockInterval = calcInterval(api);
const blockIntervalInSec = blockInterval.toNumber() / 1000;

const calculateValidatorAPY = useCallback(async (validatorAddress: string) => {
if (!api) {
return;
}

// Define the number of past eras you want to check (e.g., last 10 eras)
const eraDepth = 10;
const decimal = new BN(10 ** api.registry.chainDecimals[0]);
let totalRewards = BN_ZERO;
let totalRewards = 0;
let totalPoints = BN_ZERO;
let validatorPoints = BN_ZERO;
let totalStaked = BN_ZERO;
const validatorEraInfo: ValidatorEraInfo[] = [];

const { eraLength } = await api.derive.session.progress();

const currentEra = ((await api.query['staking']['activeEra']()) as Option<PalletStakingActiveEraInfo>).unwrap().index.toNumber();
const { commission } = await api.query['staking']['validators'](validatorAddress) as PalletStakingValidatorPrefs;

const eraLengthInHrs = eraLength.toNumber() * blockIntervalInSec / 3600; // 3600 = 1hr in seconds
const eraPerDay = 24 / eraLengthInHrs;
const eraDepth = 10 * eraPerDay; // eras to calculate

// Loop over the past eras to calculate rewards for the validator
for (let eraIndex = currentEra - eraDepth; eraIndex <= currentEra; eraIndex++) {
let netReward;
Expand All @@ -61,7 +69,7 @@ export default function useValidatorApy (api: ApiPromise | undefined, validatorA
totalPoints = totalPoints.add(eraPoints.total);
const _eraReward = eraReward.unwrap();

netReward = _eraReward.mul(validatorPoints).div(totalPoints).muln(100 - (commission.toNumber() / 1e7)).div(BN_HUNDRED);
netReward = _eraReward.toNumber() * (validatorPoints.toNumber() / totalPoints.toNumber()) * (100 - (commission.toNumber() / 1e7)) / 100;
} else {
continue;
}
Expand Down Expand Up @@ -92,19 +100,16 @@ export default function useValidatorApy (api: ApiPromise | undefined, validatorA
}

validatorEraInfo.forEach(({ netReward, total }) => {
totalRewards = totalRewards.add(netReward);
totalRewards += netReward;
totalStaked = totalStaked.add(total);
});

const actualDepth = validatorEraInfo.length;

totalStaked = totalStaked.div(decimal).divn(actualDepth);

const dailyReward = totalRewards.div(decimal).divn(actualDepth);

// Calculate daily return as a fraction of the staked amount
const dailyReturn = dailyReward.toNumber() / totalStaked.toNumber();

const dailyReward = (totalRewards / decimal.toNumber() / actualDepth) * eraPerDay;
const dailyReturn = dailyReward / totalStaked.toNumber();
const APY = (dailyReturn * 365 * 100).toFixed(2);

if (!isFinite(+APY) || isNaN(+APY)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/extension/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1467,4 +1467,4 @@
"Menu options": "",
"PolkaGate logo": "",
"Total balance": ""
}
}
Loading