feat(2025): validator table broken into sub-tables (#2290)

* feat(2025): validator table broken into sub-tables and showing all the new data

* feat(2025): fixed test broken due to a refactor

* feat(2025): removed unused redundancy in shared.tsx

* feat(2025): tweak to types import location in consensus-validators-table.spec.tsx

* feat(2025): removed tests for deleted validator table elements
This commit is contained in:
Sam Keen 2022-12-01 11:39:15 +00:00 committed by GitHub
parent cf2cfad746
commit a30f2227c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 316 additions and 167 deletions

View File

@ -12,8 +12,6 @@ const stakedByDelegates = '[data-testid="staked-by-delegates"]';
const stakeShare = '[data-testid="stake-percentage"]';
const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d*\.?\d*$/;
const ownStake = '[data-testid="own-stake"]';
const nominatedStake = '[data-testid="nominated-stake"]';
context('Staking Page - verify elements on page', function () {
before('navigate to staking page', function () {
@ -173,16 +171,6 @@ context('Staking Page - verify elements on page', function () {
});
});
// 2001-STKE-013
it('Should be able to see own stake this epoch', function () {
cy.get(ownStake).invoke('text').should('match', stakeNumberRegex);
});
// 2001-STKE-014
it('Should be able to see nominated stake this epoch', function () {
cy.get(nominatedStake).invoke('text').should('match', stakeNumberRegex);
});
// 2001-STKE-011 2002-SINC-001 2002-SINC-002
it('Should be able to see epoch information', function () {
const epochTitle = 'h3';

View File

@ -537,7 +537,8 @@
"unknownEthereumConnectionError": "An unknown error occurred. Check the console in your browser's web developer tools for more details",
"stakingDescriptionTitle": "How does staking on Vega work?",
"stakingDescription1": "1. VEGA is an ERC20 token. Associate it with a Vega wallet using the",
"stakingDescription2": "2. Use this site and your Vega wallet to nominate a validator. View the validator profile pitches and discussion",
"stakingDescription2a": "2. Use this site and your Vega wallet to nominate a validator.",
"stakingDescription2b": "View the validator profile pitches and discussion",
"stakingDescription3": "3. Earn a share of trading fees and treasury rewards for each full epoch staked",
"stakingDescription4": "4. Move your stake if your validator is penalised",
"stakingBridge": "staking bridge",

View File

@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { EpochCountdown } from '../../../components/epoch-countdown';
import { useNodesQuery } from './__generated___/Nodes';
import { usePreviousEpochQuery } from './__generated___/PreviousEpoch';
import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch';
import { ValidatorTables } from './validator-tables';
export const EpochData = () => {

View File

@ -32,7 +32,7 @@ export const StakingIntro = () => {
</Link>
</li>
<li>
{t('stakingDescription2')}{' '}
{t('stakingDescription2a')} {t('stakingDescription2b')}{' '}
<UTLink
href={ExternalLinks.VALIDATOR_FORUM}
target="_blank"

View File

@ -4,13 +4,13 @@ import { ConsensusValidatorsTable } from './consensus-validators-table';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { NodesDocument } from '../__generated___/Nodes';
import { PreviousEpochDocument } from '../__generated___/PreviousEpoch';
import { PreviousEpochDocument } from '../../__generated___/PreviousEpoch';
import { Schema } from '@vegaprotocol/types';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import type { MockedResponse } from '@apollo/client/testing';
import type { PartialDeep } from 'type-fest';
import type { NodesFragmentFragment } from '../__generated___/Nodes';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch';
const nodeFactory = (
overrides?: PartialDeep<NodesFragmentFragment>

View File

@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { AgGridDynamic as AgGrid, Button } from '@vegaprotocol/ui-toolkit';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import { normalisedVotingPower, rawValidatorScore } from '../../shared';
import {
defaultColDef,
NODE_LIST_GRID_STYLES,
@ -47,8 +49,8 @@ export const ConsensusValidatorsTable = ({
const canonisedNodes = data
.sort((a, b) => {
const aVotingPower = toBigNum(a.rankingScore.votingPower, 0);
const bVotingPower = toBigNum(b.rankingScore.votingPower, 0);
const aVotingPower = new BigNumber(a.rankingScore.votingPower);
const bVotingPower = new BigNumber(b.rankingScore.votingPower);
return bVotingPower.minus(aVotingPower).toNumber();
})
.map((node, index) => {
@ -69,9 +71,6 @@ export const ConsensusValidatorsTable = ({
pendingStake,
votingPowerRanking,
}) => {
const normalisedVotingPower =
toBigNum(votingPower, 0).dividedBy(100).dp(2).toString() + '%';
return {
id,
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
@ -83,11 +82,11 @@ export const ConsensusValidatorsTable = ({
toBigNum(stakedTotal, decimals),
2
),
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
[ValidatorFields.NORMALISED_VOTING_POWER]:
normalisedVotingPower(votingPower),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties(
previousEpochData,
id,
rawValidatorScore(previousEpochData, id),
performanceScore,
stakedTotal,
totalStake

View File

@ -1,3 +1,4 @@
import { rawValidatorScore } from '../../shared';
import { stakedTotalPercentage, totalPenalties } from './shared';
const mockPreviousEpochData = {
@ -27,19 +28,34 @@ describe('stakedTotalPercentage', () => {
describe('totalPenalties', () => {
it('should return the correct penalty based on arbitrary values, test 1', () => {
expect(
totalPenalties(mockPreviousEpochData, '0x123', '0.1', '5000', '100000')
totalPenalties(
rawValidatorScore(mockPreviousEpochData, '0x123'),
'0.1',
'5000',
'100000'
)
).toBe('50%');
});
it('should return the correct penalty based on lower performance score than first test', () => {
expect(
totalPenalties(mockPreviousEpochData, '0x123', '0.05', '5000', '100000')
totalPenalties(
rawValidatorScore(mockPreviousEpochData, '0x123'),
'0.05',
'5000',
'100000'
)
).toBe('75%');
});
it('should return the correct penalty based on higher amount of stake than other tests (great penalty due to anti-whaling)', () => {
expect(
totalPenalties(mockPreviousEpochData, '0x123', '0.1', '5000', '5500')
totalPenalties(
rawValidatorScore(mockPreviousEpochData, '0x123'),
'0.1',
'5000',
'5500'
)
).toBe('97.25%');
});
});

View File

@ -1,9 +1,9 @@
import { Link } from 'react-router-dom';
import { removePaginationWrapper, toBigNum } from '@vegaprotocol/react-helpers';
import { useTranslation } from 'react-i18next';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { Button } from '@vegaprotocol/ui-toolkit';
import type { NodesFragmentFragment } from '../__generated___/Nodes';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
import { useTranslation } from 'react-i18next';
import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch';
export enum ValidatorFields {
RANKING_INDEX = 'rankingIndex',
@ -37,19 +37,11 @@ export const stakedTotalPercentage = (stakeScore: string) =>
toBigNum(stakeScore, 0).times(100).dp(2).toString() + '%';
export const totalPenalties = (
previousEpochData: PreviousEpochQuery | undefined,
id: string,
rawValidatorScore: string | null | undefined,
performanceScore: string,
stakedTotal: string,
totalStake: string
) => {
const rawValidatorScore = previousEpochData
? removePaginationWrapper(
previousEpochData.epoch?.validatorsConnection?.edges
).find((validator) => validator?.id === id)?.rewardScore
?.rawValidatorScore
: null;
const totalPenaltiesCalc =
rawValidatorScore !== null
? 100 *

View File

@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import { rawValidatorScore } from '../../shared';
import {
defaultColDef,
NODE_LIST_GRID_STYLES,
@ -49,9 +51,9 @@ export const StandbyPendingValidatorsTable = ({
let individualStakeNeededForPromotion = undefined;
if (stakeNeededForPromotion) {
const stakedTotalBigNum = toBigNum(stakedTotal, 0);
const stakeNeededBigNum = toBigNum(stakeNeededForPromotion, 0);
const performanceScoreBigNum = toBigNum(performanceScore, 0);
const stakedTotalBigNum = new BigNumber(stakedTotal);
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
const performanceScoreBigNum = new BigNumber(performanceScore);
const calc = stakeNeededBigNum
.dividedBy(performanceScoreBigNum)
@ -76,8 +78,7 @@ export const StandbyPendingValidatorsTable = ({
individualStakeNeededForPromotion || t('n/a'),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties(
previousEpochData,
id,
rawValidatorScore(previousEpochData, id),
performanceScore,
stakedTotal,
totalStake

View File

@ -7,7 +7,7 @@ import type {
NodesQuery,
NodesFragmentFragment,
} from '../__generated___/Nodes';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
import type { PreviousEpochQuery } from '../../__generated___/PreviousEpoch';
import { formatNumber } from '../../../../lib/format-number';
import {
createDocsLinks,

View File

@ -2,6 +2,12 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
addDecimal,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/react-helpers';
import { EpochCountdown } from '../../../components/epoch-countdown';
import { BigNumber } from '../../../lib/bignumber';
import { ConnectToVega } from '../../../components/connect-to-vega';
@ -9,41 +15,39 @@ import { StakingForm } from './staking-form';
import { ValidatorTable } from './validator-table';
import { YourStake } from './your-stake';
import NodeContainer from './nodes-container';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import {
addDecimal,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/react-helpers';
import type { StakingQuery } from './__generated___/Staking';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
interface StakingNodeProps {
data?: StakingQuery;
previousEpochData?: PreviousEpochQuery;
}
export const StakingNode = ({ data }: StakingNodeProps) => {
export const StakingNode = ({ data, previousEpochData }: StakingNodeProps) => {
const { pubKey: vegaKey } = useVegaWallet();
const {
appState: { decimals },
} = useAppState();
const { node } = useParams<{ node: string }>();
const { t } = useTranslation();
const nodeInfo = React.useMemo(
() =>
removePaginationWrapper(data?.nodesConnection?.edges).find(
const { nodeInfo, currentEpoch, delegations } = React.useMemo(
() => ({
nodeInfo: removePaginationWrapper(data?.nodesConnection?.edges).find(
({ id }) => id === node
),
[node, data]
);
const currentEpoch = React.useMemo(() => {
return data?.epoch.id;
}, [data?.epoch.id]);
const delegations = React.useMemo(
() => removePaginationWrapper(data?.party?.delegationsConnection?.edges),
[data?.party?.delegationsConnection?.edges]
currentEpoch: data?.epoch.id,
delegations: removePaginationWrapper(
data?.party?.delegationsConnection?.edges
),
}),
[
data?.epoch.id,
data?.nodesConnection?.edges,
data?.party?.delegationsConnection?.edges,
node,
]
);
const stakeThisEpoch = React.useMemo(() => {
@ -105,7 +109,7 @@ export const StakingNode = ({ data }: StakingNodeProps) => {
<ValidatorTable
node={nodeInfo}
stakedTotal={addDecimal(data?.nodeData?.stakedTotal || '0', decimals)}
stakeThisEpoch={stakeThisEpoch}
previousEpochData={previousEpochData}
/>
</section>
{data?.epoch.timestamps.start && data?.epoch.timestamps.expiry && (
@ -148,6 +152,10 @@ export const StakingNode = ({ data }: StakingNodeProps) => {
export const Node = () => {
return (
<NodeContainer>{({ data }) => <StakingNode data={data} />}</NodeContainer>
<NodeContainer>
{({ data, previousEpochData }) => (
<StakingNode data={data} previousEpochData={previousEpochData} />
)}
</NodeContainer>
);
};

View File

@ -5,7 +5,9 @@ import { useTranslation } from 'react-i18next';
import { useStakingQuery } from './__generated___/Staking';
import { SplashLoader } from '../../../components/splash-loader';
import { usePreviousEpochQuery } from '../__generated___/PreviousEpoch';
import type { StakingQuery } from './__generated___/Staking';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
// TODO should only request a single node. When migrating from deprecated APIs we should address this.
@ -14,14 +16,28 @@ const RPC_ERROR = 'rpc error: code = NotFound desc = NotFound error';
export const NodeContainer = ({
children,
}: {
children: ({ data }: { data?: StakingQuery }) => React.ReactElement;
children: ({
data,
previousEpochData,
}: {
data?: StakingQuery;
previousEpochData?: PreviousEpochQuery;
}) => React.ReactElement;
}) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { data, loading, error, refetch } = useStakingQuery({
variables: { partyId: pubKey || '' },
});
const { data: previousEpochData } = usePreviousEpochQuery({
variables: {
epochId: (Number(data?.epoch.id) - 1).toString(),
},
skip: !data?.epoch.id,
});
// @todo - this epoch querying needs to be hoisted as the validator tables rely
// on it too
React.useEffect(() => {
const interval = setInterval(() => {
if (!data?.epoch.timestamps.expiry) return;
@ -59,7 +75,7 @@ export const NodeContainer = ({
);
}
return children({ data });
return children({ data, previousEpochData });
};
export default NodeContainer;

View File

@ -1,15 +1,20 @@
import React from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from '@vegaprotocol/ui-toolkit';
import countryData from '../../../components/country-selector/country-data';
import { Link as UTLink, Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number';
import type { StakingNodeFieldsFragment } from './__generated___/Staking';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { ExternalLinks, toBigNum } from '@vegaprotocol/react-helpers';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import { Schema } from '@vegaprotocol/types';
import { totalPenalties } from '../home/validator-tables/shared';
import { normalisedVotingPower, rawValidatorScore } from '../shared';
import type { ReactNode } from 'react';
import type { StakingNodeFieldsFragment } from './__generated___/Staking';
import type { PreviousEpochQuery } from '../__generated___/PreviousEpoch';
const statuses = {
[Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_ERSATZ]: 'status-ersatz',
@ -25,10 +30,10 @@ const ValidatorTableCell = ({
children,
dataTestId,
}: {
children: React.ReactNode;
children: ReactNode;
dataTestId?: string;
}) => (
<span datatest-id={dataTestId} className="break-words">
<span data-testid={dataTestId} className="break-words">
{children}
</span>
);
@ -36,109 +41,214 @@ const ValidatorTableCell = ({
export interface ValidatorTableProps {
node: StakingNodeFieldsFragment;
stakedTotal: string;
stakeThisEpoch: BigNumber;
stakedTotalAllNodes?: string;
previousEpochData?: PreviousEpochQuery;
}
export const ValidatorTable = ({
node,
stakedTotal,
stakeThisEpoch,
previousEpochData,
}: ValidatorTableProps) => {
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
const {
appState: { decimals },
} = useAppState();
const stakePercentage = React.useMemo(() => {
const total = new BigNumber(stakedTotal);
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const stakedTotalPercentage =
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
? '-'
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
return stakedTotalPercentage;
}, [decimals, node.stakedTotal, stakedTotal]);
const total = useMemo(() => new BigNumber(stakedTotal), [stakedTotal]);
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const location = countryData.find((c) => c.code === node.location)?.name;
const performanceScore = new BigNumber(node.rankingScore.performanceScore).dp(
2
);
const validatorScore = rawValidatorScore(previousEpochData, node.id);
const overstakedAmount = useMemo(() => {
const amount = validatorScore
? new BigNumber(validatorScore).times(total).minus(stakedOnNode).dp(2)
: new BigNumber(0);
return amount.isNegative() ? new BigNumber(0) : amount;
}, [stakedOnNode, total, validatorScore]);
const stakePercentage =
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
? '-'
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
const performancePenalty =
new BigNumber(1).minus(performanceScore).times(100).toString() + '%';
const totalPenaltiesAmount = totalPenalties(
validatorScore,
node.rankingScore.performanceScore,
stakedOnNode.toString(),
total.toString()
);
const overstakingPenalty = useMemo(
() =>
overstakedAmount.dividedBy(stakedOnNode).times(100).dp(2).toString() +
'%',
[overstakedAmount, stakedOnNode]
);
const unnormalisedVotingPower = validatorScore
? new BigNumber(validatorScore).times(100).dp(2).toString() + '%'
: null;
return (
<KeyValueTable data-testid="validator-table">
<KeyValueTableRow>
<span>{t('id')}:</span>
<ValidatorTableCell dataTestId="validator-id">
{node.id}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
<ValidatorTableCell dataTestId="validator-public-key">
{node.pubkey}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('ABOUT THIS VALIDATOR')}</span>
<span>
<a href={node.infoUrl}>{node.infoUrl}</a>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STATUS')}</span>
<span data-testid="validator-status">
{t(statusTranslationKey(node.rankingScore.status))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('IP ADDRESS')}</span>
<span>{node.location}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('ETHEREUM ADDRESS')}</span>
<span>
<Link
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
target="_blank"
>
{node.ethereumAddress}
</Link>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('TOTAL STAKE')}</span>
<span data-testid="total-stake">
{formatNumber(toBigNum(node.stakedTotal, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PENDING STAKE')}</span>
<span data-testid="pending-stake">
{formatNumber(toBigNum(node.pendingStake, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKED BY OPERATOR')}</span>
<span data-testid="staked-by-operator">
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKED BY DELEGATES')}</span>
<span data-testid="staked-by-delegates">
{formatNumber(toBigNum(node.stakedByDelegates, 18))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKE SHARE')}</span>
<span data-testid="stake-percentage">{stakePercentage}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('OWN STAKE (THIS EPOCH)')}</span>
<span data-testid="own-stake">{formatNumber(stakeThisEpoch)}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('NOMINATED (THIS EPOCH)')}</span>
<span data-testid="nominated-stake">
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
</span>
</KeyValueTableRow>
</KeyValueTable>
<div className="mb-8" data-testid="validator-table">
<KeyValueTable data-testid="validator-table-profile" title={t('PROFILE')}>
<KeyValueTableRow>
<span>{t('id')}</span>
<ValidatorTableCell dataTestId="validator-id">
{node.id}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('ABOUT THIS VALIDATOR')}</span>
<span>
<a href={node.infoUrl}>{node.infoUrl}</a>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>
<strong>{t('STATUS')}</strong>
</span>
<span data-testid="validator-status">
<strong>{t(statusTranslationKey(node.rankingScore.status))}</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
<div className="mb-6 text-sm">
{t('stakingDescription2b')}{' '}
<UTLink
href={ExternalLinks.VALIDATOR_FORUM}
target="_blank"
data-testid="validator-forum-link"
>
{t('onTheForum')}
</UTLink>
</div>
<KeyValueTable data-testid="validator-table-address" title={t('ADDRESS')}>
<KeyValueTableRow>
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
<ValidatorTableCell dataTestId="validator-public-key">
{node.pubkey}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('SERVER LOCATION')}</span>
<ValidatorTableCell>
{location || t('not available')}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('ETHEREUM ADDRESS')}</span>
<span>
<Link
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
target="_blank"
>
{node.ethereumAddress}
</Link>
</span>
</KeyValueTableRow>
</KeyValueTable>
<KeyValueTable data-testid="validator-table-stake" title={t('STAKE')}>
<KeyValueTableRow>
<span>{t('STAKED BY OPERATOR')}</span>
<span data-testid="staked-by-operator">
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKED BY DELEGATES')}</span>
<span data-testid="staked-by-delegates">
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>
<strong>{t('TOTAL STAKE')}</strong>
</span>
<span data-testid="total-stake">
<strong>
{formatNumber(toBigNum(node.stakedTotal, decimals))}
</strong>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PENDING STAKE')}</span>
<span data-testid="pending-stake">
{formatNumber(toBigNum(node.pendingStake, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKE SHARE')}</span>
<span data-testid="stake-percentage">{stakePercentage}</span>
</KeyValueTableRow>
</KeyValueTable>
<KeyValueTable
data-testid="validator-table-penalties"
title={t('PENALTIES')}
>
<KeyValueTableRow>
<span>{t('OVERSTAKED AMOUNT')}</span>
<span>{overstakedAmount.toString()}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('OVERSTAKED PENALTY')}</span>
<span>{overstakingPenalty}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PERFORMANCE SCORE')}</span>
<span>{performanceScore.toString()}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PERFORMANCE PENALITY')}</span>
<span>{performancePenalty}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>
<strong>{t('TOTAL PENALTIES')}</strong>
</span>
<span>
<strong>{totalPenaltiesAmount}</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
<KeyValueTable
data-testid="validator-table-voting-power"
title={t('VOTING POWER')}
>
<KeyValueTableRow>
<span>{t('UNNORMALISED VOTING POWER')}</span>
<span>{unnormalisedVotingPower}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>
<strong>{t('NORMALISED VOTING POWER')}</strong>
</span>
<span>
<strong>
{normalisedVotingPower(node.rankingScore.votingPower)}
</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
</div>
);
};

View File

@ -0,0 +1,18 @@
import { removePaginationWrapper, toBigNum } from '@vegaprotocol/react-helpers';
import type { PreviousEpochQuery } from './__generated___/PreviousEpoch';
export const rawValidatorScore = (
previousEpochData: PreviousEpochQuery | undefined,
id: string
) => {
return previousEpochData
? removePaginationWrapper(
previousEpochData.epoch?.validatorsConnection?.edges
).find((validator) => validator?.id === id)?.rewardScore
?.rawValidatorScore
: null;
};
export const normalisedVotingPower = (votingPower: string) => {
return toBigNum(votingPower, 0).dividedBy(100).dp(2).toString() + '%';
};