From 597dfd0a44d698730699e2205930808ed7775274 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 2 Aug 2020 10:06:37 +0200 Subject: [PATCH] Slash breakdown (#3315) --- packages/page-staking/src/Slashes/Row.tsx | 57 +++++++++++++++++++ packages/page-staking/src/Slashes/index.tsx | 61 +++++++++++++++++++++ packages/page-staking/src/Slashes/types.ts | 13 +++++ packages/page-staking/src/index.tsx | 24 ++++++-- 4 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 packages/page-staking/src/Slashes/Row.tsx create mode 100644 packages/page-staking/src/Slashes/index.tsx create mode 100644 packages/page-staking/src/Slashes/types.ts diff --git a/packages/page-staking/src/Slashes/Row.tsx b/packages/page-staking/src/Slashes/Row.tsx new file mode 100644 index 0000000000..7e527bdffe --- /dev/null +++ b/packages/page-staking/src/Slashes/Row.tsx @@ -0,0 +1,57 @@ +// Copyright 2017-2020 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Slash } from './types'; + +import React from 'react'; +import { AddressMini, AddressSmall } from '@polkadot/react-components'; +import { FormatBalance } from '@polkadot/react-query'; +import { formatNumber } from '@polkadot/util'; + +interface Props { + slash: Slash; + withEra: boolean; +} + +function Row ({ slash: { era, slash: { others, own, payout, reporters, validator }, total }, withEra }: Props): React.ReactElement { + return ( + + +

{withEra && formatNumber(era)}

+ + + + + + + + + + + + {others.map(([accountId, balance], index): React.ReactNode => ( + + ))} + + + {reporters.map((reporter, index): React.ReactNode => ( + + ))} + + + + + + ); +} + +export default React.memo(Row); diff --git a/packages/page-staking/src/Slashes/index.tsx b/packages/page-staking/src/Slashes/index.tsx new file mode 100644 index 0000000000..6ab6e92c94 --- /dev/null +++ b/packages/page-staking/src/Slashes/index.tsx @@ -0,0 +1,61 @@ +// Copyright 2017-2020 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { UnappliedSlash } from '@polkadot/types/interfaces'; +import { Slash } from './types'; + +import BN from 'bn.js'; +import React, { useMemo } from 'react'; +import { Table } from '@polkadot/react-components'; + +import { useTranslation } from '../translate'; +import Row from './Row'; + +interface Props { + slashes: [BN, UnappliedSlash[]][]; +} + +function Slashes ({ slashes }: Props): React.ReactElement | null { + const { t } = useTranslation(); + const header = useMemo(() => [ + [t('unapplied slashes'), 'start', 2], + [t('own')], + [t('total')], + [t('nominators'), 'start'], + [t('reporters'), 'start'], + [t('payout')] + ], [t]); + const rows = useMemo((): Slash[] => { + return slashes + .reduce((rows: Slash[], [era, slashes]): Slash[] => { + return slashes.reduce((rows: Slash[], slash): Slash[] => { + const total = slash.others.reduce((total: BN, [, value]): BN => { + return total.add(value); + }, slash.own); + + rows.push({ era, slash, total }); + + return rows; + }, rows); + }, []) + .sort((a, b) => b.era.cmp(a.era)); + }, [slashes]); + + return ( + ('There an no unapplied/pending slashes')} + header={header} + > + {rows.map((slash, index): React.ReactNode => ( + + ))} +
+ ); +} + +export default React.memo(Slashes); diff --git a/packages/page-staking/src/Slashes/types.ts b/packages/page-staking/src/Slashes/types.ts new file mode 100644 index 0000000000..28734d1878 --- /dev/null +++ b/packages/page-staking/src/Slashes/types.ts @@ -0,0 +1,13 @@ +// Copyright 2017-2020 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { UnappliedSlash } from '@polkadot/types/interfaces'; + +import BN from 'bn.js'; + +export interface Slash { + era: BN; + slash: UnappliedSlash; + total: BN; +} diff --git a/packages/page-staking/src/index.tsx b/packages/page-staking/src/index.tsx index be1c35064c..afe16cfda1 100644 --- a/packages/page-staking/src/index.tsx +++ b/packages/page-staking/src/index.tsx @@ -12,7 +12,7 @@ import { useLocation } from 'react-router-dom'; import styled from 'styled-components'; import { HelpOverlay } from '@polkadot/react-components'; import Tabs from '@polkadot/react-components/Tabs'; -import { useAccounts, useApi, useCall, useFavorites, useOwnStashInfos, useStashIds } from '@polkadot/react-hooks'; +import { useAccounts, useApi, useAvailableSlashes, useCall, useFavorites, useOwnStashInfos, useStashIds } from '@polkadot/react-hooks'; import { isFunction } from '@polkadot/util'; import basicMd from './md/basic.md'; @@ -21,6 +21,7 @@ import Overview from './Overview'; import Payouts from './Payouts'; import Query from './Query'; import Summary from './Overview/Summary'; +import Slashes from './Slashes'; import Targets from './Targets'; import { STORE_FAVS_BASE } from './constants'; import { useTranslation } from './translate'; @@ -40,6 +41,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement(api.derive.staking.overview, []); const isInElection = useCall(api.query.staking?.eraElectionStatus, [], { @@ -74,6 +76,10 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement('Waiting') }, + { + name: 'slashes', + text: t('Slashes') + }, { hasParams: true, name: 'query', @@ -81,13 +87,18 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement !!q), [api, t]); const hiddenTabs = useMemo( - (): string[] => - !hasAccounts + (): string[] => { + const base = !hasAccounts ? ['actions', 'payouts', 'query'] : !hasQueries ? ['returns', 'query'] - : [], - [hasAccounts, hasQueries] + : []; + + return slashes.length + ? base + : base.concat('slashes'); + }, + [hasAccounts, hasQueries, slashes] ); useEffect((): void => { @@ -120,6 +131,9 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement + + +