Slash breakdown (#3315)

This commit is contained in:
Jaco Greeff 2020-08-02 10:06:37 +02:00 committed by GitHub
parent 0d631759ae
commit 597dfd0a44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 150 additions and 5 deletions

View File

@ -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<Props> {
return (
<tr>
<td className='number'>
<h1>{withEra && formatNumber(era)}</h1>
</td>
<td className='address'>
<AddressSmall value={validator} />
</td>
<td className='number together'>
<FormatBalance value={own} />
</td>
<td className='number together'>
<FormatBalance value={total} />
</td>
<td className='address all'>
{others.map(([accountId, balance], index): React.ReactNode => (
<AddressMini
balance={balance}
key={index}
value={accountId}
withBalance
/>
))}
</td>
<td className='address'>
{reporters.map((reporter, index): React.ReactNode => (
<AddressMini
key={index}
value={reporter}
/>
))}
</td>
<td className='number together'>
<FormatBalance value={payout} />
</td>
</tr>
);
}
export default React.memo(Row);

View File

@ -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<Props> | 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 (
<Table
empty={t<string>('There an no unapplied/pending slashes')}
header={header}
>
{rows.map((slash, index): React.ReactNode => (
<Row
key={`${index}-${slash.era.toString()}`}
slash={slash}
withEra={index === 0 || !slash.era.eq(rows[index - 1].era)}
/>
))}
</Table>
);
}
export default React.memo(Slashes);

View File

@ -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;
}

View File

@ -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<Pr
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
const allStashes = useStashIds();
const ownStashes = useOwnStashInfos();
const slashes = useAvailableSlashes();
const targets = useSortedTargets(favorites);
const stakingOverview = useCall<DeriveStakingOverview>(api.derive.staking.overview, []);
const isInElection = useCall<boolean>(api.query.staking?.eraElectionStatus, [], {
@ -74,6 +76,10 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
name: 'waiting',
text: t<string>('Waiting')
},
{
name: 'slashes',
text: t<string>('Slashes')
},
{
hasParams: true,
name: 'query',
@ -81,13 +87,18 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
}
].filter((q): q is { name: string; text: string } => !!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<Pr
<Route path={[`${basePath}/query/:value`, `${basePath}/query`]}>
<Query />
</Route>
<Route path={`${basePath}/slashes`}>
<Slashes slashes={slashes} />
</Route>
<Route path={`${basePath}/targets`}>
<Targets
isInElection={isInElection}