From d78770a0941356317d81b6a74440ff232276f2bd Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 30 Aug 2020 14:25:20 +0200 Subject: [PATCH] Oversubscriptions by size index (#3573) --- .../src/Actions/Account/ListNominees.tsx | 7 ++++++- .../page-staking/src/Actions/useInactives.ts | 18 ++++++++++++------ packages/page-staking/src/Ledgend.tsx | 5 ++++- packages/page-staking/src/MaxBadge.tsx | 11 ++++++++--- packages/page-staking/src/Targets/index.tsx | 9 +++++---- packages/page-staking/src/constants.ts | 4 +++- 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/page-staking/src/Actions/Account/ListNominees.tsx b/packages/page-staking/src/Actions/Account/ListNominees.tsx index 70f3cdc01f..6916aa9a16 100644 --- a/packages/page-staking/src/Actions/Account/ListNominees.tsx +++ b/packages/page-staking/src/Actions/Account/ListNominees.tsx @@ -4,7 +4,9 @@ import React from 'react'; import { AddressMini, Expander } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; +import { MAX_NOM_PAYOUTS } from '../../constants'; import { useTranslation } from '../../translate'; import useInactives from '../useInactives'; @@ -15,13 +17,16 @@ interface Props { function ListNominees ({ nominating, stashId }: Props): React.ReactElement { const { t } = useTranslation(); + const { api } = useApi(); const { nomsActive, nomsChilled, nomsInactive, nomsOver, nomsWaiting } = useInactives(stashId, nominating); + const max = (api.consts.staking?.maxNominatorRewardedPerValidator || MAX_NOM_PAYOUTS).toString(); + return ( <> {nomsOver && nomsOver.length !== 0 && ( ('The validators that are over-subscribed, only the top 64 backing stake will be rewarded.')} + help={t('These validators are active but only the top {{max}} nominators by backing stake will be receiving rewards. The nominating stash is not one of those to be rewarded in the current era.', { replace: max })} summary={t('Oversubscribed nominations ({{count}})', { replace: { count: nomsOver.length } })} > {nomsOver.map((nomineeId, index): React.ReactNode => ( diff --git a/packages/page-staking/src/Actions/useInactives.ts b/packages/page-staking/src/Actions/useInactives.ts index 9c8f717faf..d5c0eb8438 100644 --- a/packages/page-staking/src/Actions/useInactives.ts +++ b/packages/page-staking/src/Actions/useInactives.ts @@ -6,9 +6,12 @@ import { DeriveSessionIndexes } from '@polkadot/api-derive/types'; import { EraIndex, Exposure, Nominations, SlashingSpans } from '@polkadot/types/interfaces'; import { useEffect, useState } from 'react'; +import { ApiPromise } from '@polkadot/api'; import { useApi, useCall, useIsMountedRef } from '@polkadot/react-hooks'; import { Option } from '@polkadot/types'; +import { MAX_NOM_PAYOUTS } from '../constants'; + interface Inactives { nomsActive?: string[]; nomsChilled?: string[]; @@ -17,7 +20,9 @@ interface Inactives { nomsWaiting?: string[]; } -function extractState (stashId: string, slashes: Option[], nominees: string[], activeEra: EraIndex, submittedIn: EraIndex, exposures: Exposure[]): Inactives { +function extractState (api: ApiPromise, stashId: string, slashes: Option[], nominees: string[], activeEra: EraIndex, submittedIn: EraIndex, exposures: Exposure[]): Inactives { + const max = (api.consts.staking?.maxNominatorRewardedPerValidator || MAX_NOM_PAYOUTS); + // chilled const nomsChilled = nominees.filter((_, index): boolean => { if (slashes[index].isNone) { @@ -31,10 +36,11 @@ function extractState (stashId: string, slashes: Option[], nomine // all nominations that are oversubscribed const nomsOver = exposures - .map((exposure, index) => - exposure.others.length > 64 - ? nominees[index] - : null + .map(({ others }) => others.sort((a, b) => b.value.unwrap().cmp(a.value.unwrap()))) + .map((others, index) => + max.gtn(others.map(({ who }) => who.toString()).indexOf(stashId)) + ? null + : nominees[index] ) .filter((nominee): nominee is string => !!nominee && !nomsChilled.includes(nominee)); @@ -98,7 +104,7 @@ export default function useInactives (stashId: string, nominees?: string[]): Ina const slashes = exposuresAndSpans.slice(nominees.length) as Option[]; mountedRef.current && setState( - extractState(stashId, slashes, nominees, indexes.activeEra, optNominators.unwrapOrDefault().submittedIn, exposures) + extractState(api, stashId, slashes, nominees, indexes.activeEra, optNominators.unwrapOrDefault().submittedIn, exposures) ); } ) diff --git a/packages/page-staking/src/Ledgend.tsx b/packages/page-staking/src/Ledgend.tsx index 775f243070..1455377e6a 100644 --- a/packages/page-staking/src/Ledgend.tsx +++ b/packages/page-staking/src/Ledgend.tsx @@ -5,7 +5,9 @@ import React from 'react'; import styled from 'styled-components'; import { Badge, Icon } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; +import { MAX_NOM_PAYOUTS } from './constants'; import { useTranslation } from './translate'; interface Props { @@ -14,6 +16,7 @@ interface Props { function Ledgend ({ className }: Props): React.ReactElement { const { t } = useTranslation(); + const { api } = useApi(); return (
@@ -48,7 +51,7 @@ function Ledgend ({ className }: Props): React.ReactElement { {t('Oversubscribed')} diff --git a/packages/page-staking/src/MaxBadge.tsx b/packages/page-staking/src/MaxBadge.tsx index dae093903b..4a92276629 100644 --- a/packages/page-staking/src/MaxBadge.tsx +++ b/packages/page-staking/src/MaxBadge.tsx @@ -4,22 +4,27 @@ import React from 'react'; import { Badge } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; -import { MAX_PAYOUTS } from './constants'; +import { MAX_NOM_PAYOUTS } from './constants'; interface Props { numNominators?: number; } function MaxBadge ({ numNominators }: Props): React.ReactElement | null { - if (!numNominators || (numNominators < MAX_PAYOUTS)) { + const { api } = useApi(); + + const max = (api.consts.staking?.maxNominatorRewardedPerValidator || MAX_NOM_PAYOUTS); + + if (!numNominators || max.gtn(numNominators)) { return null; } return ( ); } diff --git a/packages/page-staking/src/Targets/index.tsx b/packages/page-staking/src/Targets/index.tsx index e241600c87..b1319d7f73 100644 --- a/packages/page-staking/src/Targets/index.tsx +++ b/packages/page-staking/src/Targets/index.tsx @@ -9,11 +9,11 @@ import { SortedTargets, TargetSortBy, ValidatorInfo } from '../types'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Button, Icon, InputBalance, Table, Toggle } from '@polkadot/react-components'; -import { useAvailableSlashes } from '@polkadot/react-hooks'; +import { useApi, useAvailableSlashes } from '@polkadot/react-hooks'; import ElectionBanner from '../ElectionBanner'; import Filtering from '../Filtering'; -import { MAX_NOMINATIONS } from '../constants'; +import { MAX_NOMINATIONS, MAX_NOM_PAYOUTS } from '../constants'; import { useTranslation } from '../translate'; import Nominate from './Nominate'; import Summary from './Summary'; @@ -67,6 +67,7 @@ function extractNominees (ownNominators: StakerState[] = []): string[] { function Targets ({ className = '', isInElection, ownStashes, targets: { avgStaked, calcWith, lastReward, lowStaked, nominators, setCalcWith, totalStaked, validators }, toggleFavorite }: Props): React.ReactElement { const { t } = useTranslation(); + const { api } = useApi(); const allSlashes = useAvailableSlashes(); const ownNominators = useOwnNominators(ownStashes); const [selected, setSelected] = useState([]); @@ -110,14 +111,14 @@ function Targets ({ className = '', isInElection, ownStashes, targets: { avgStak const _selectProfitable = useCallback( () => setSelected( (validators || []).reduce((result: string[], { hasIdentity, isElected, isFavorite, key, numNominators, rewardPayout }): string[] => { - if ((numNominators < 64) && (result.length < MAX_NOMINATIONS) && (hasIdentity || !withIdentity) && (isElected || isFavorite) && !rewardPayout.isZero()) { + if ((api.consts.staking?.maxNominatorRewardedPerValidator || MAX_NOM_PAYOUTS).gtn(numNominators) && (result.length < MAX_NOMINATIONS) && (hasIdentity || !withIdentity) && (isElected || isFavorite) && !rewardPayout.isZero()) { result.push(key); } return result; }, []) ), - [validators, withIdentity] + [api, validators, withIdentity] ); const labels = useMemo( diff --git a/packages/page-staking/src/constants.ts b/packages/page-staking/src/constants.ts index 71d0b3f922..8e48eb4eab 100644 --- a/packages/page-staking/src/constants.ts +++ b/packages/page-staking/src/constants.ts @@ -2,8 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import BN from 'bn.js'; + export const MAX_NOMINATIONS = 16; -export const MAX_PAYOUTS = 64; +export const MAX_NOM_PAYOUTS = new BN(64); export const STORE_FAVS_BASE = 'staking:favorites';