Skip to content

Commit b3655a6

Browse files
committed
Fix edge scenarios by introducing timer and null
1 parent c3122b2 commit b3655a6

File tree

8 files changed

+109
-45
lines changed

8 files changed

+109
-45
lines changed

packages/page-staking/src/Overview/SummaryGeneral.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,17 @@ totalStaked } }: Props) {
8585
>{inflation.toFixed(1)}%</SpinnerWrap>
8686
</CardSummary>
8787
<CardSummary label={t<string>('returns')}>
88-
<SpinnerWrap
89-
check={totalIssuance && (stakedReturn > 0) && Number.isFinite(stakedReturn)}
90-
>
91-
{stakedReturn.toFixed(1)}%
92-
</SpinnerWrap>
88+
{stakedReturn !== null
89+
? <SpinnerWrap
90+
check={
91+
totalIssuance &&
92+
(stakedReturn && stakedReturn > 0) &&
93+
Number.isFinite(stakedReturn)}
94+
// eslint-disable-next-line @typescript-eslint/indent
95+
>
96+
{stakedReturn.toFixed(1)}%
97+
</SpinnerWrap>
98+
: '0%'}
9399
</CardSummary>
94100
</Section>
95101
</SummaryBox>

packages/page-staking/src/Overview/SummaryNominators.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
6464
help={t<string>('Number of nominators backing active validators in the current era.')}
6565
label={t<string>('active')}
6666
>
67-
<SpinnerWrap check={nominatorActiveCount}>
68-
{formatNumber(nominatorActiveCount)}
69-
</SpinnerWrap>
67+
{nominatorActiveCount !== null
68+
? <SpinnerWrap check={nominatorActiveCount}>
69+
{formatNumber(nominatorActiveCount)}
70+
</SpinnerWrap>
71+
: '-'}
7072
</CardSummary>
7173
</Section>
7274
</SummaryBox>
@@ -91,9 +93,11 @@ function SummaryNominators ({ targets: { maxNominatorsCount,
9193
help={t<string>('Minimum threshold stake among active nominators.')}
9294
label={t<string>('min active thrs')}
9395
>
94-
<SpinnerWrap check={nominatorMinActiveThreshold}>
95-
{nominatorMinActiveThreshold}
96-
</SpinnerWrap>
96+
{nominatorMinActiveThreshold !== null
97+
? <SpinnerWrap check={nominatorMinActiveThreshold}>
98+
{nominatorMinActiveThreshold}
99+
</SpinnerWrap>
100+
: '-'}
97101
</CardSummary>
98102
</Section>
99103
<Section>

packages/page-staking/src/Overview/SummaryValidators.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ function SummaryValidators ({ targets:
6161
help={t<string>('Count of active validators.')}
6262
label={t<string>('active')}
6363
>
64-
<SpinnerWrap check={validatorActiveCount}>
65-
{validatorActiveCount}
66-
</SpinnerWrap>
64+
{validatorActiveCount !== null
65+
? <SpinnerWrap check={validatorActiveCount}>
66+
{validatorActiveCount}
67+
</SpinnerWrap>
68+
: '-'}
6769
</CardSummary>
6870
</Section>
6971
</SummaryBox>
@@ -88,9 +90,11 @@ function SummaryValidators ({ targets:
8890
help={t<string>('Minimum threshold stake among active validators.')}
8991
label={t<string>('min active thrs')}
9092
>
91-
<SpinnerWrap check={validatorMinActiveThreshold}>
92-
{validatorMinActiveThreshold}
93-
</SpinnerWrap>
93+
{validatorMinActiveThreshold !== null
94+
? <SpinnerWrap check={validatorMinActiveThreshold}>
95+
{validatorMinActiveThreshold}
96+
</SpinnerWrap>
97+
: '-'}
9498
</CardSummary>
9599
</Section>
96100
<Section>

packages/page-staking/src/types.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export interface ValidatorInfo extends ValidatorInfoRank {
6666
numNominators: number;
6767
numRecentPayouts: number;
6868
skipRewards: boolean;
69-
stakedReturn: number;
69+
stakedReturn: number | null;
7070
stakedReturnCmp: number;
7171
validatorPrefs?: ValidatorPrefs | ValidatorPrefsTo196;
7272
withReturns?: boolean;
@@ -96,15 +96,15 @@ export interface SortedTargets {
9696
validators?: ValidatorInfo[];
9797
validatorIds?: string[];
9898
waitingIds?: string[];
99-
nominatorActiveCount?: number;
100-
nominatorElectingCount?: number;
99+
nominatorActiveCount?: number | null;
100+
nominatorElectingCount?: number | null;
101101
nominatorIntentionCount?: number;
102-
validatorActiveCount?: number;
102+
validatorActiveCount?: number | null;
103103
validatorIntentionCount?: number;
104104
validatorWaitingCount?: number;
105-
nominatorMinActiveThreshold?: string;
105+
nominatorMinActiveThreshold?: string | null;
106106
nominatorMaxElectingCount?: u32 | null;
107-
validatorMinActiveThreshold?: string;
107+
validatorMinActiveThreshold?: string | null;
108108
}
109109

110110
export interface PoolAccounts {

packages/page-staking/src/useSortedTargets.ts

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { SortedTargets, TargetSortBy, ValidatorInfo } from './types';
99

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

12-
import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation } from '@polkadot/react-hooks';
12+
import { createNamedHook, useAccounts, useApi, useCall, useCallMulti, useInflation, usePrevious } from '@polkadot/react-hooks';
1313
import { AccountId32 } from '@polkadot/types/interfaces';
1414
import { PalletStakingExposure, PalletStakingIndividualExposure } from '@polkadot/types/lookup';
1515
import { arrayFlatten, BN, BN_HUNDRED, BN_MAX_INTEGER, BN_ONE, BN_ZERO, formatBalance } from '@polkadot/util';
@@ -57,10 +57,10 @@ const OPT_MULTI = {
5757
historyDepth,
5858
maxNominatorsCount: optMaxNominatorsCount && optMaxNominatorsCount.isSome
5959
? optMaxNominatorsCount.unwrap()
60-
: undefined,
60+
: new BN(0),
6161
maxValidatorsCount: optMaxValidatorsCount && optMaxValidatorsCount.isSome
6262
? optMaxValidatorsCount.unwrap()
63-
: undefined,
63+
: new BN(0),
6464
minNominatorBond,
6565
minValidatorBond,
6666
totalIssuance
@@ -192,7 +192,7 @@ function extractSingle (api: ApiPromise, allAccounts: string[], derive: DeriveSt
192192
rankOverall: 0,
193193
rankReward: 0,
194194
skipRewards,
195-
stakedReturn: 0,
195+
stakedReturn: null,
196196
stakedReturnCmp: 0,
197197
validatorPrefs,
198198
withReturns
@@ -212,7 +212,7 @@ function addReturns (inflation: Inflation, baseInfo: Partial<SortedTargets>): Pa
212212

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

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

303-
const [calcStakers, setCalcStakers] = useState<boolean>(false);
303+
const [timerDone, setTimerDone] = useState<boolean>(false);
304+
305+
const prevStakersLength = usePrevious(stakers.length);
306+
307+
useEffect(() => {
308+
// This timer is set to wait for 10 seconds in order to identify
309+
// if api has finished loading the staking info. If at the end of this timer
310+
// the values from API are not yet loaded, then it is assumed that the
311+
// API is not returning any values (meaning probable misconfiguration).
312+
// This is for covering edge cases (e.g. staking pallet is included
313+
// in apps but not used - stakers = 0).
314+
const apiTimer = setTimeout(() => setTimerDone(true), 10000);
315+
316+
return () => {
317+
clearTimeout(apiTimer);
318+
};
319+
}, []);
304320

305321
useEffect(() => {
306-
if (stakers[0] && stakers[0][1]) {
322+
if (prevStakersLength !== stakers.length && stakers[0] && stakers[0][1]) {
307323
setStakersTotal(stakers[0][1].total.toBn());
324+
} else if (stakers.length === 0) {
325+
if (timerDone) {
326+
setStakersTotal(null);
327+
}
308328
}
309-
}, [stakers]);
329+
}, [prevStakersLength, stakers, timerDone]);
310330

311331
useEffect(() => {
312-
if (stakers.length && !calcStakers) {
332+
if (stakers.length !== prevStakersLength) {
313333
const assignments: Map<string, BN> = new Map();
314334

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

320340
assignments.set(nominator, val ? amount.toBn().add(val) : amount.toBn());
321341
});
322-
323342
const nominatorStakes = Array.from(assignments);
324343

325344
nominatorStakes.sort((a, b) => a[1].cmp(b[1]));
326-
327345
setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters);
328-
329346
setNominatorElectingCount(assignments.size);
330347
setNominatorActiveCount(assignments.size);
331348
setNominatorMinActiveThreshold(nominatorStakes[0] ? b(nominatorStakes[0][1], api) : '');
332349
setValidatorActiveCount(stakers.length);
333-
setCalcStakers(true);
350+
} else if (stakers.length === 0) {
351+
if (timerDone) {
352+
setNominatorMaxElectingCount(api.consts.electionProviderMultiPhase?.maxElectingVoters);
353+
setNominatorElectingCount(null);
354+
setNominatorActiveCount(null);
355+
setNominatorMinActiveThreshold(null);
356+
setValidatorActiveCount(null);
357+
}
334358
}
335-
}, [api, calcStakers, stakers]);
359+
}, [api, prevStakersLength, stakers, timerDone]);
336360

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

344368
const inflation = useInflation(baseInfo?.totalStaked);
345369

370+
useEffect(() => {
371+
if (!inflation.stakedReturn && timerDone) {
372+
inflation.stakedReturn = null;
373+
}
374+
}, [inflation, inflation.stakedReturn, timerDone]);
375+
346376
const curEra = useCall<Option<u32>>(api.query.staking.currentEra);
347377

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

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

354-
const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : '';
384+
const validatorMinActiveThreshold = stakersTotal ? b(stakersTotal, api) : null;
355385

356386
return useMemo(
357387
(): SortedTargets => ({
@@ -372,7 +402,7 @@ function useSortedTargetsImpl (favorites: string[], withLedger: boolean): Sorted
372402
validatorActiveCount,
373403
validatorMinActiveThreshold,
374404
...(
375-
inflation && inflation.stakedReturn
405+
inflation && (inflation.stakedReturn !== null && inflation.stakedReturn)
376406
? addReturns(inflation, baseInfo)
377407
: baseInfo
378408
)

packages/react-hooks/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export { usePopupWindow } from './usePopupWindow';
5353
export { useProxies } from './useProxies';
5454
export { useIsParasLinked, useParaEndpoints } from './useParaEndpoints';
5555
export { usePassword } from './usePassword';
56+
export { usePrevious } from './usePrevious';
5657
export { useRegistrars } from './useRegistrars';
5758
export { useSavedFlags } from './useSavedFlags';
5859
export { useScroll } from './useScroll';

packages/react-hooks/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface Inflation {
4040
idealInterest: number;
4141
inflation: number;
4242
stakedFraction: number;
43-
stakedReturn: number;
43+
stakedReturn: number | null;
4444
}
4545

4646
export interface Slash {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2017-2022 @polkadot/react-hooks authors & contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { useEffect, useRef } from 'react';
5+
6+
import { createNamedHook } from './createNamedHook';
7+
8+
function usePreviousImpl (v: any) {
9+
const ref = useRef();
10+
11+
useEffect(() => {
12+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
13+
ref.current = v;
14+
}, [v]);
15+
16+
return ref.current;
17+
}
18+
19+
export const usePrevious = createNamedHook('usePopupWindow', usePreviousImpl);

0 commit comments

Comments
 (0)