Skip to content

Commit

Permalink
Fix edge scenarios by introducing timer and null
Browse files Browse the repository at this point in the history
  • Loading branch information
wirednkod committed Jun 22, 2022
1 parent c3122b2 commit b3655a6
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 45 deletions.
16 changes: 11 additions & 5 deletions packages/page-staking/src/Overview/SummaryGeneral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,17 @@ totalStaked } }: Props) {
>{inflation.toFixed(1)}%</SpinnerWrap>
</CardSummary>
<CardSummary label={t<string>('returns')}>
<SpinnerWrap
check={totalIssuance && (stakedReturn > 0) && Number.isFinite(stakedReturn)}
>
{stakedReturn.toFixed(1)}%
</SpinnerWrap>
{stakedReturn !== null
? <SpinnerWrap
check={
totalIssuance &&
(stakedReturn && stakedReturn > 0) &&
Number.isFinite(stakedReturn)}
// eslint-disable-next-line @typescript-eslint/indent
>
{stakedReturn.toFixed(1)}%
</SpinnerWrap>
: '0%'}
</CardSummary>
</Section>
</SummaryBox>
Expand Down
16 changes: 10 additions & 6 deletions packages/page-staking/src/Overview/SummaryNominators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
help={t<string>('Number of nominators backing active validators in the current era.')}
label={t<string>('active')}
>
<SpinnerWrap check={nominatorActiveCount}>
{formatNumber(nominatorActiveCount)}
</SpinnerWrap>
{nominatorActiveCount !== null
? <SpinnerWrap check={nominatorActiveCount}>
{formatNumber(nominatorActiveCount)}
</SpinnerWrap>
: '-'}
</CardSummary>
</Section>
</SummaryBox>
Expand All @@ -91,9 +93,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
help={t<string>('Minimum threshold stake among active nominators.')}
label={t<string>('min active thrs')}
>
<SpinnerWrap check={nominatorMinActiveThreshold}>
{nominatorMinActiveThreshold}
</SpinnerWrap>
{nominatorMinActiveThreshold !== null
? <SpinnerWrap check={nominatorMinActiveThreshold}>
{nominatorMinActiveThreshold}
</SpinnerWrap>
: '-'}
</CardSummary>
</Section>
<Section>
Expand Down
16 changes: 10 additions & 6 deletions packages/page-staking/src/Overview/SummaryValidators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ function SummaryValidators ({ targets:
help={t<string>('Count of active validators.')}
label={t<string>('active')}
>
<SpinnerWrap check={validatorActiveCount}>
{validatorActiveCount}
</SpinnerWrap>
{validatorActiveCount !== null
? <SpinnerWrap check={validatorActiveCount}>
{validatorActiveCount}
</SpinnerWrap>
: '-'}
</CardSummary>
</Section>
</SummaryBox>
Expand All @@ -88,9 +90,11 @@ function SummaryValidators ({ targets:
help={t<string>('Minimum threshold stake among active validators.')}
label={t<string>('min active thrs')}
>
<SpinnerWrap check={validatorMinActiveThreshold}>
{validatorMinActiveThreshold}
</SpinnerWrap>
{validatorMinActiveThreshold !== null
? <SpinnerWrap check={validatorMinActiveThreshold}>
{validatorMinActiveThreshold}
</SpinnerWrap>
: '-'}
</CardSummary>
</Section>
<Section>
Expand Down
12 changes: 6 additions & 6 deletions packages/page-staking/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export interface ValidatorInfo extends ValidatorInfoRank {
numNominators: number;
numRecentPayouts: number;
skipRewards: boolean;
stakedReturn: number;
stakedReturn: number | null;
stakedReturnCmp: number;
validatorPrefs?: ValidatorPrefs | ValidatorPrefsTo196;
withReturns?: boolean;
Expand Down Expand Up @@ -96,15 +96,15 @@ export interface SortedTargets {
validators?: ValidatorInfo[];
validatorIds?: string[];
waitingIds?: string[];
nominatorActiveCount?: number;
nominatorElectingCount?: number;
nominatorActiveCount?: number | null;
nominatorElectingCount?: number | null;
nominatorIntentionCount?: number;
validatorActiveCount?: number;
validatorActiveCount?: number | null;
validatorIntentionCount?: number;
validatorWaitingCount?: number;
nominatorMinActiveThreshold?: string;
nominatorMinActiveThreshold?: string | null;
nominatorMaxElectingCount?: u32 | null;
validatorMinActiveThreshold?: string;
validatorMinActiveThreshold?: string | null;
}

export interface PoolAccounts {
Expand Down
72 changes: 51 additions & 21 deletions packages/page-staking/src/useSortedTargets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { SortedTargets, TargetSortBy, ValidatorInfo } from './types';

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

import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks';
import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation, usePrevious } from '@polkadot/react-hooks';
import { AccountId32 } from '@polkadot/types/interfaces';
import { PalletStakingExposure, PalletStakingIndividualExposure } from '@polkadot/types/lookup';
import { arrayFlatten, BN, BN_HUNDRED, BN_MAX_INTEGER, BN_ONE, BN_ZERO, formatBalance } from '@polkadot/util';
Expand Down Expand Up @@ -57,10 +57,10 @@ const OPT_MULTI = {
historyDepth,
maxNominatorsCount: optMaxNominatorsCount && optMaxNominatorsCount.isSome
? optMaxNominatorsCount.unwrap()
: undefined,
: new BN(0),
maxValidatorsCount: optMaxValidatorsCount && optMaxValidatorsCount.isSome
? optMaxValidatorsCount.unwrap()
: undefined,
: new BN(0),
minNominatorBond,
minValidatorBond,
totalIssuance
Expand Down Expand Up @@ -192,7 +192,7 @@ function extractSingle (api: ApiPromise, allAccounts: string[], derive: DeriveSt
rankOverall: 0,
rankReward: 0,
skipRewards,
stakedReturn: 0,
stakedReturn: null,
stakedReturnCmp: 0,
validatorPrefs,
withReturns
Expand All @@ -212,7 +212,7 @@ function addReturns (inflation: Inflation, baseInfo: Partial<SortedTargets>): Pa

avgStaked && !avgStaked.isZero() && validators.forEach((v): void => {
if (!v.skipRewards && v.withReturns) {
const adjusted = avgStaked.mul(BN_HUNDRED).imuln(inflation.stakedReturn).div(v.bondTotal);
const adjusted = avgStaked.mul(BN_HUNDRED).imuln(inflation.stakedReturn || 0).div(v.bondTotal);

// in some cases, we may have overflows... protect against those
v.stakedReturn = (adjusted.gt(BN_MAX_INTEGER) ? BN_MAX_INTEGER : adjusted).toNumber() / BN_HUNDRED.toNumber();
Expand Down Expand Up @@ -293,23 +293,43 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted
const waitingInfo = useCall<DeriveStakingWaiting>(api.derive.staking.waitingInfo, [{ ...DEFAULT_FLAGS_WAITING, withLedger }]);
const lastEraInfo = useCall<LastEra>(api.derive.session.info, undefined, OPT_ERA);
const [stakers, setStakers] = useState<[StorageKey<[u32, AccountId32]>, PalletStakingExposure][]>([]);
const [stakersTotal, setStakersTotal] = useState<BN | undefined>();
const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState<string>('');
const [stakersTotal, setStakersTotal] = useState<BN | undefined | null>();
const [nominatorMinActiveThreshold, setNominatorMinActiveThreshold] = useState<string | null>('');
const [nominatorMaxElectingCount, setNominatorMaxElectingCount] = useState<u32>();
const [nominatorElectingCount, setNominatorElectingCount] = useState<number | undefined>();
const [nominatorActiveCount, setNominatorActiveCount] = useState<number | undefined>();
const [validatorActiveCount, setValidatorActiveCount] = useState<number | undefined>();
const [nominatorElectingCount, setNominatorElectingCount] = useState<number | undefined | null>();
const [nominatorActiveCount, setNominatorActiveCount] = useState<number | undefined | null>();
const [validatorActiveCount, setValidatorActiveCount] = useState<number | undefined | null>();

const [calcStakers, setCalcStakers] = useState<boolean>(false);
const [timerDone, setTimerDone] = useState<boolean>(false);

const prevStakersLength = usePrevious(stakers.length);

useEffect(() => {
// This timer is set to wait for 10 seconds in order to identify
// if api has finished loading the staking info. If at the end of this timer
// the values from API are not yet loaded, then it is assumed that the
// API is not returning any values (meaning probable misconfiguration).
// This is for covering edge cases (e.g. staking pallet is included
// in apps but not used - stakers = 0).
const apiTimer = setTimeout(() => setTimerDone(true), 10000);

return () => {
clearTimeout(apiTimer);
};
}, []);

useEffect(() => {
if (stakers[0] && stakers[0][1]) {
if (prevStakersLength !== stakers.length && stakers[0] && stakers[0][1]) {
setStakersTotal(stakers[0][1].total.toBn());
} else if (stakers.length === 0) {
if (timerDone) {
setStakersTotal(null);
}
}
}, [stakers]);
}, [prevStakersLength, stakers, timerDone]);

useEffect(() => {
if (stakers.length && !calcStakers) {
if (stakers.length !== prevStakersLength) {
const assignments: Map<string, BN> = new Map();

stakers.sort((a, b) => a[1].total.toBn().cmp(b[1].total.toBn())).map((x) => x[1].others).flat(1).forEach((x) => {
Expand All @@ -319,20 +339,24 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted

assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn());
});

const nominatorStakes = Array.from(assignments);

nominatorStakes.sort((a, b) => a[1].cmp(b[1]));

setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters);

setNominatorElectingCount(assignments.size);
setNominatorActiveCount(assignments.size);
setNominatorMinActiveThreshold(nominatorStakes[0] ? b(nominatorStakes[0][1], api) : '');
setValidatorActiveCount(stakers.length);
setCalcStakers(true);
} else if (stakers.length === 0) {
if (timerDone) {
setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters);
setNominatorElectingCount(null);
setNominatorActiveCount(null);
setNominatorMinActiveThreshold(null);
setValidatorActiveCount(null);
}
}
}, [api, calcStakers, stakers]);
}, [api, prevStakersLength, stakers, timerDone]);

const baseInfo = useMemo(
() => electedInfo && lastEraInfo && totalIssuance && waitingInfo
Expand All @@ -343,6 +367,12 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted

const inflation = useInflation(baseInfo?.totalStaked);

useEffect(() => {
if (!inflation.stakedReturn && timerDone) {
inflation.stakedReturn = null;
}
}, [inflation, inflation.stakedReturn, timerDone]);

const curEra = useCall<Option<u32>>(api.query.staking.currentEra);

const getStakers = useMemo(() => async (currentEra: u32) => {
Expand All @@ -351,7 +381,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted

curEra && getStakers(curEra?.unwrap());

const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : '';
const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : null;

return useMemo(
(): SortedTargets => ({
Expand All @@ -372,7 +402,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted
validatorActiveCount,
validatorMinActiveThreshold,
...(
inflation && inflation.stakedReturn
inflation && (inflation.stakedReturn !== null && inflation.stakedReturn)
? addReturns(inflation, baseInfo)
: baseInfo
)
Expand Down
1 change: 1 addition & 0 deletions packages/react-hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export { usePopupWindow } from './usePopupWindow';
export { useProxies } from './useProxies';
export { useIsParasLinked, useParaEndpoints } from './useParaEndpoints';
export { usePassword } from './usePassword';
export { usePrevious } from './usePrevious';
export { useRegistrars } from './useRegistrars';
export { useSavedFlags } from './useSavedFlags';
export { useScroll } from './useScroll';
Expand Down
2 changes: 1 addition & 1 deletion packages/react-hooks/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface Inflation {
idealInterest: number;
inflation: number;
stakedFraction: number;
stakedReturn: number;
stakedReturn: number | null;
}

export interface Slash {
Expand Down
19 changes: 19 additions & 0 deletions packages/react-hooks/src/usePrevious.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2017-2022 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { useEffect, useRef } from 'react';

import { createNamedHook } from './createNamedHook';

function usePreviousImpl (v: any) {
const ref = useRef();

useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
ref.current = v;
}, [v]);

return ref.current;
}

export const usePrevious = createNamedHook('usePopupWindow', usePreviousImpl);

0 comments on commit b3655a6

Please sign in to comment.