From 299c95eb3dbdb4ea5baa10c830af9164eb9bac64 Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 17 Jun 2021 09:27:19 +0300 Subject: [PATCH] Remove Substrate 2.0 generation payouts (#5612) * Remove Subtrate 2.0 generation payouts * Display available totals * Empty available * No need to check for batch availability * Adjust headers * Adjust views --- packages/page-staking/src/Payouts/Stash.tsx | 51 +----- packages/page-staking/src/Payouts/index.tsx | 159 ++++++++++-------- packages/page-staking/src/Payouts/types.ts | 1 + .../src/Payouts/useStakerPayouts.ts | 25 --- packages/react-hooks/src/useOwnEraRewards.ts | 14 +- 5 files changed, 106 insertions(+), 144 deletions(-) delete mode 100644 packages/page-staking/src/Payouts/useStakerPayouts.ts diff --git a/packages/page-staking/src/Payouts/Stash.tsx b/packages/page-staking/src/Payouts/Stash.tsx index 57b8a74c6f..c7f6ba69c0 100644 --- a/packages/page-staking/src/Payouts/Stash.tsx +++ b/packages/page-staking/src/Payouts/Stash.tsx @@ -2,26 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 import type BN from 'bn.js'; -import type { SubmittableExtrinsic } from '@polkadot/api/types'; -import type { DeriveStakerReward, DeriveStakingAccount } from '@polkadot/api-derive/types'; import type { PayoutStash } from './types'; import React, { useEffect, useState } from 'react'; -import { ApiPromise } from '@polkadot/api'; -import { AddressSmall, TxButton } from '@polkadot/react-components'; -import { useApi, useCall, useTxBatch } from '@polkadot/react-hooks'; +import { AddressSmall } from '@polkadot/react-components'; import { BlockToTime, FormatBalance } from '@polkadot/react-query'; -import { useTranslation } from '../translate'; import useEraBlocks from './useEraBlocks'; import { createErasString } from './util'; interface Props { className?: string; - isDisabled?: boolean; payout: PayoutStash; - stakerPayoutsAfter: BN; } interface EraInfo { @@ -29,43 +22,17 @@ interface EraInfo { oldestEra?: BN; } -// TODO This is pre Substrate 2.0, remove -function createPrevPayouts (api: ApiPromise, payoutRewards: DeriveStakerReward[]): SubmittableExtrinsic<'promise'>[] { - return payoutRewards.map(({ era, isValidator, nominating }) => - isValidator - ? api.tx.staking.payoutValidator(era) - : api.tx.staking.payoutNominator(era, nominating.map(({ validatorId, validatorIndex }): [string, number] => - [validatorId, validatorIndex] - )) - ); -} - -function Stash ({ className = '', isDisabled, payout: { available, rewards, stashId }, stakerPayoutsAfter }: Props): React.ReactElement | null { - const { t } = useTranslation(); - const { api } = useApi(); - const [payoutTxs, setPayoutTxs] = useState[]>([]); - const batchTx = useTxBatch(payoutTxs); +function Stash ({ className = '', payout: { available, rewards, stashId } }: Props): React.ReactElement { const [{ eraStr, oldestEra }, setEraInfo] = useState({ eraStr: '' }); const eraBlocks = useEraBlocks(oldestEra); - const stakingAccount = useCall(api.derive.staking.account, [stashId]); useEffect((): void => { rewards && setEraInfo({ - eraStr: createErasString(rewards.map(({ era }): BN => era)), + eraStr: createErasString(rewards.map(({ era }) => era)), oldestEra: rewards[0]?.era }); }, [rewards]); - useEffect((): void => { - rewards && setPayoutTxs( - createPrevPayouts(api, rewards.filter(({ era }) => era.lt(stakerPayoutsAfter))) - ); - }, [api, rewards, stakerPayoutsAfter]); - - if (available.isZero()) { - return null; - } - return ( - {batchTx && stakingAccount && ( - ('Payout')} - /> - )} - + /> ); } diff --git a/packages/page-staking/src/Payouts/index.tsx b/packages/page-staking/src/Payouts/index.tsx index 2db7a1ebf2..a5cfefe022 100644 --- a/packages/page-staking/src/Payouts/index.tsx +++ b/packages/page-staking/src/Payouts/index.tsx @@ -14,13 +14,12 @@ import { ApiPromise } from '@polkadot/api'; import { Button, Table, ToggleGroup } from '@polkadot/react-components'; import { useApi, useCall, useOwnEraRewards } from '@polkadot/react-hooks'; import { FormatBalance } from '@polkadot/react-query'; -import { BN_ZERO, isFunction } from '@polkadot/util'; +import { BN_THREE } from '@polkadot/util'; import ElectionBanner from '../ElectionBanner'; import { useTranslation } from '../translate'; import PayButton from './PayButton'; import Stash from './Stash'; -import useStakerPayouts from './useStakerPayouts'; import Validator from './Validator'; interface Props { @@ -30,8 +29,10 @@ interface Props { } interface Available { - stashTotal?: BN | null; + stashAvail?: BN | null; stashes?: PayoutStash[]; + valAvail?: BN | null; + valTotal?: BN | null; validators?: PayoutValidator[]; } @@ -42,16 +43,15 @@ interface EraSelection { const DAY_SECS = new BN(1000 * 60 * 60 * 24); -function groupByValidator (allRewards: Record, stakerPayoutsAfter: BN): PayoutValidator[] { +function groupByValidator (allRewards: Record): PayoutValidator[] { return Object .entries(allRewards) .reduce((grouped: PayoutValidator[], [stashId, rewards]): PayoutValidator[] => { rewards - .filter(({ era }) => era.gte(stakerPayoutsAfter)) .forEach((reward): void => { Object .entries(reward.validators) - .forEach(([validatorId, { value }]): void => { + .forEach(([validatorId, { total, value }]): void => { const entry = grouped.find((entry) => entry.validatorId === validatorId); if (entry) { @@ -67,6 +67,7 @@ function groupByValidator (allRewards: Record, sta } entry.available = entry.available.add(value); + entry.total = entry.total.add(total); } else { grouped.push({ available: value, @@ -74,6 +75,7 @@ function groupByValidator (allRewards: Record, sta era: reward.era, stashes: { [stashId]: value } }], + total, validatorId }); } @@ -100,17 +102,23 @@ function extractStashes (allRewards: Record): Payo .sort((a, b) => b.available.cmp(a.available)); } -function getAvailable (allRewards: Record | null | undefined, stakerPayoutsAfter: BN): Available { +function getAvailable (allRewards: Record | null | undefined): Available { if (allRewards) { const stashes = extractStashes(allRewards); - const stashTotal = stashes.length - ? stashes.reduce((total: BN, { available }) => total.add(available), BN_ZERO) + const validators = groupByValidator(allRewards); + const stashAvail = stashes.length + ? stashes.reduce((a, { available }) => a.iadd(available), new BN(0)) : null; + const [valAvail, valTotal] = validators.length + ? validators.reduce<[BN, BN]>(([a, t], { available, total }) => [a.iadd(available), t.iadd(total)], [new BN(0), new BN(0)]) + : [null, null]; return { - stashTotal, + stashAvail, stashes, - validators: groupByValidator(allRewards, stakerPayoutsAfter) + valAvail, + valTotal, + validators }; } @@ -118,47 +126,50 @@ function getAvailable (allRewards: Record | null | } function getOptions (api: ApiPromise, eraLength: BN | undefined, historyDepth: BN | undefined, t: TFunction): EraSelection[] { - if (eraLength && historyDepth) { - const blocksPerDay = DAY_SECS.div(api.consts.babe?.expectedBlockTime || api.consts.timestamp?.minimumPeriod.muln(2) || new BN(6000)); - const maxBlocks = eraLength.mul(historyDepth); - const eraSelection: EraSelection[] = []; - let days = 2; + if (!eraLength || !historyDepth) { + return [{ text: '', value: 0 }]; + } - while (true) { - const dayBlocks = blocksPerDay.muln(days); + const blocksPerDay = DAY_SECS.div( + api.consts.babe?.expectedBlockTime || + api.consts.timestamp?.minimumPeriod.muln(2) || + new BN(6000) + ); + const maxBlocks = eraLength.mul(historyDepth); + const eraSelection: EraSelection[] = []; + const days = new BN(2); - if (dayBlocks.gte(maxBlocks)) { - break; - } + while (true) { + const dayBlocks = blocksPerDay.mul(days); - eraSelection.push({ - text: t('{{days}} days', { replace: { days } }), - value: dayBlocks.div(eraLength).toNumber() - }); - - days = days * 3; + if (dayBlocks.gte(maxBlocks)) { + break; } eraSelection.push({ - text: t('Max, {{eras}} eras', { replace: { eras: historyDepth.toNumber() } }), - value: historyDepth.toNumber() + text: t('{{days}} days', { replace: { days: days.toString() } }), + value: dayBlocks.div(eraLength).toNumber() }); - return eraSelection; + days.imul(BN_THREE); } - return [{ text: '', value: 0 }]; + eraSelection.push({ + text: t('Max, {{eras}} eras', { replace: { eras: historyDepth.toNumber() } }), + value: historyDepth.toNumber() + }); + + return eraSelection; } function Payouts ({ className = '', isInElection, ownValidators }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const [hasOwnValidators] = useState(() => ownValidators.length !== 0); - const [myStashesIndex, setMyStashesIndex] = useState(() => (isFunction(api.tx.staking.payoutStakers) && hasOwnValidators) ? 0 : 1); + const [myStashesIndex, setMyStashesIndex] = useState(() => hasOwnValidators ? 0 : 1); const [eraSelectionIndex, setEraSelectionIndex] = useState(0); const eraLength = useCall(api.derive.session.eraLength); const historyDepth = useCall(api.query.staking.historyDepth); - const stakerPayoutsAfter = useStakerPayouts(); const eraSelection = useMemo( () => getOptions(api, eraLength, historyDepth, t), @@ -167,15 +178,15 @@ function Payouts ({ className = '', isInElection, ownValidators }: Props): React const { allRewards, isLoadingRewards } = useOwnEraRewards(eraSelection[eraSelectionIndex].value, myStashesIndex ? undefined : ownValidators); - const { stashTotal, stashes, validators } = useMemo( - () => getAvailable(allRewards, stakerPayoutsAfter), - [allRewards, stakerPayoutsAfter] + const { stashAvail, stashes, valAvail, validators } = useMemo( + () => getAvailable(allRewards), + [allRewards] ); const headerStashes = useMemo(() => [ [myStashesIndex ? t('payout/stash') : t('overall/validator'), 'start', 2], [t('eras'), 'start'], - [t('available')], + [myStashesIndex ? t('own') : t('total')], [('remaining')], [undefined, undefined, 3] ], [myStashesIndex, t]); @@ -183,7 +194,7 @@ function Payouts ({ className = '', isInElection, ownValidators }: Props): React const headerValidatorsRef = useRef([ [t('payout/validator'), 'start', 2], [t('eras'), 'start'], - [t('available')], + [t('own')], [('remaining')], [undefined, undefined, 3] ]); @@ -193,71 +204,79 @@ function Payouts ({ className = '', isInElection, ownValidators }: Props): React { text: t('My stashes'), value: 'all' } ], [hasOwnValidators, t]); - const footer = useMemo(() => ( + const footerStash = useMemo(() => ( - {stashTotal && } + {stashAvail && } - ), [stashTotal]); + ), [stashAvail]); - const hasPayouts = isFunction(api.tx.staking.payoutStakers); - const isDisabled = isInElection || !isFunction(api.tx.utility?.batch); + const footerVal = useMemo(() => ( + + + + {valAvail && } + + + + ), [valAvail]); return (
- {hasPayouts && ( - - - - - - )} + + + + + - {hasPayouts && !isLoadingRewards && !stashes?.length && ( + {!isLoadingRewards && !stashes?.length && (

{t('Payouts of rewards for a validator can be initiated by any account. This means that as soon as a validator or nominator requests a payout for an era, all the nominators for that validator will be rewarded. Each user does not need to claim individually and the suggestion is that validators should claim rewards for everybody as soon as an era ends.')}

{t('If you have not claimed rewards straight after the end of the era, the validator is in the active set and you are seeing no rewards, this would mean that the reward payout transaction was made by another account on your behalf. Always check your favorite explorer to see any historic payouts made to your accounts.')}

)} ('No pending payouts for your stashes')} + empty={!isLoadingRewards && stashes && ( + myStashesIndex + ? t('No pending payouts for your stashes') + : t('No pending payouts for your validators') + )} emptySpinner={t('Retrieving info for the selected eras, this will take some time')} - footer={footer} + footer={footerStash} header={headerStashes} isFixed > {!isLoadingRewards && stashes?.map((payout): React.ReactNode => ( ))}
- {hasPayouts && (myStashesIndex === 1) && !isLoadingRewards && validators && (validators.length !== 0) && ( + {(myStashesIndex === 1) && !isLoadingRewards && validators && (validators.length !== 0) && ( - {!isLoadingRewards && validators.map((payout): React.ReactNode => ( + {!isLoadingRewards && validators.filter(({ available }) => !available.isZero()).map((payout): React.ReactNode => ( diff --git a/packages/page-staking/src/Payouts/types.ts b/packages/page-staking/src/Payouts/types.ts index a93b8af6e4..1fd1b04f16 100644 --- a/packages/page-staking/src/Payouts/types.ts +++ b/packages/page-staking/src/Payouts/types.ts @@ -14,6 +14,7 @@ export interface PayoutValidator { available: BN; eras: PayoutEraValidator[]; validatorId: string; + total: BN; } export interface PayoutStash { diff --git a/packages/page-staking/src/Payouts/useStakerPayouts.ts b/packages/page-staking/src/Payouts/useStakerPayouts.ts deleted file mode 100644 index ef1637a4d9..0000000000 --- a/packages/page-staking/src/Payouts/useStakerPayouts.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017-2021 @polkadot/app-staking authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type BN from 'bn.js'; -import type { Option } from '@polkadot/types'; -import type { EraIndex } from '@polkadot/types/interfaces'; - -import { useMemo } from 'react'; - -import { useApi, useCall } from '@polkadot/react-hooks'; -import { BN_BILLION, BN_ZERO, isFunction } from '@polkadot/util'; - -export default function useStakerPayouts (): BN { - const { api } = useApi(); - const migrateEraOpt = useCall>(api.query.staking?.migrateEra); - - return useMemo( - () => (migrateEraOpt && migrateEraOpt.isSome && migrateEraOpt.unwrap()) || ( - isFunction(api.tx.staking.payoutStakers) - ? BN_ZERO - : BN_BILLION - ), - [api, migrateEraOpt] - ); -} diff --git a/packages/react-hooks/src/useOwnEraRewards.ts b/packages/react-hooks/src/useOwnEraRewards.ts index feadc86ef3..3d82494e13 100644 --- a/packages/react-hooks/src/useOwnEraRewards.ts +++ b/packages/react-hooks/src/useOwnEraRewards.ts @@ -31,6 +31,16 @@ interface Filtered { validatorEras: ValidatorWithEras[]; } +const EMPTY_FILTERED: Filtered = { + filteredEras: [], + validatorEras: [] +}; + +const EMPTY_STATE: State = { + isLoadingRewards: true, + rewardCount: 0 +}; + function getRewards ([[stashIds], available]: [[string[]], DeriveStakerReward[][]]): State { const allRewards: Record = {}; @@ -93,8 +103,8 @@ export function useOwnEraRewards (maxEras?: number, ownValidators?: StakerState[ const mountedRef = useIsMountedRef(); const stashIds = useOwnStashIds(); const allEras = useCall(api.derive.staking?.erasHistoric); - const [{ filteredEras, validatorEras }, setFiltered] = useState({ filteredEras: [], validatorEras: [] }); - const [state, setState] = useState({ isLoadingRewards: true, rewardCount: 0 }); + const [{ filteredEras, validatorEras }, setFiltered] = useState(EMPTY_FILTERED); + const [state, setState] = useState(EMPTY_STATE); const stakerRewards = useCall<[[string[]], DeriveStakerReward[][]]>(!ownValidators?.length && !!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true }); const erasPoints = useCall(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasPoints, [filteredEras, false]); const erasRewards = useCall(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasRewards, [filteredEras, false]);