Feat/score changes (#696)
* chore: add query and generate types for shwoing scores * fix: add key prop * chore: add tests for new functionality * style: remove debug * Update libs/types/apollo.config.js
This commit is contained in:
parent
80e6b3b8fc
commit
c7e65080b7
@ -545,5 +545,9 @@
|
||||
"proposalTerms": "Proposal terms",
|
||||
"currentlySetTo": "Currently set to ",
|
||||
"pass": "Pass",
|
||||
"fail": "Fail"
|
||||
"fail": "Fail",
|
||||
"rankingScore": "Ranking score",
|
||||
"stakeScore": "Stake score",
|
||||
"performanceScore": "Performance score",
|
||||
"votingPower": "Voting score"
|
||||
}
|
||||
|
24
apps/token/src/routes/staking/__generated__/Nodes.ts
generated
24
apps/token/src/routes/staking/__generated__/Nodes.ts
generated
@ -25,6 +25,26 @@ export interface Nodes_nodes_epochData {
|
||||
online: number;
|
||||
}
|
||||
|
||||
export interface Nodes_nodes_rankingScore {
|
||||
__typename: "RankingScore";
|
||||
/**
|
||||
* The ranking score of the validator
|
||||
*/
|
||||
rankingScore: string;
|
||||
/**
|
||||
* The stake based score of the validator (no anti-whaling)
|
||||
*/
|
||||
stakeScore: string;
|
||||
/**
|
||||
* The performance score of the validator
|
||||
*/
|
||||
performanceScore: string;
|
||||
/**
|
||||
* The tendermint voting power of the validator (uint32)
|
||||
*/
|
||||
votingPower: string;
|
||||
}
|
||||
|
||||
export interface Nodes_nodes {
|
||||
__typename: "Node";
|
||||
/**
|
||||
@ -66,6 +86,10 @@ export interface Nodes_nodes {
|
||||
pendingStakeFormatted: string;
|
||||
epochData: Nodes_nodes_epochData | null;
|
||||
status: NodeStatus;
|
||||
/**
|
||||
* Ranking scores and status for the validator for the current epoch
|
||||
*/
|
||||
rankingScore: Nodes_nodes_rankingScore;
|
||||
}
|
||||
|
||||
export interface Nodes_nodeData {
|
||||
|
@ -100,6 +100,26 @@ export interface Staking_nodes_epochData {
|
||||
online: number;
|
||||
}
|
||||
|
||||
export interface Staking_nodes_rankingScore {
|
||||
__typename: "RankingScore";
|
||||
/**
|
||||
* The ranking score of the validator
|
||||
*/
|
||||
rankingScore: string;
|
||||
/**
|
||||
* The stake based score of the validator (no anti-whaling)
|
||||
*/
|
||||
stakeScore: string;
|
||||
/**
|
||||
* The performance score of the validator
|
||||
*/
|
||||
performanceScore: string;
|
||||
/**
|
||||
* The tendermint voting power of the validator (uint32)
|
||||
*/
|
||||
votingPower: string;
|
||||
}
|
||||
|
||||
export interface Staking_nodes {
|
||||
__typename: "Node";
|
||||
/**
|
||||
@ -145,6 +165,10 @@ export interface Staking_nodes {
|
||||
pendingStakeFormatted: string;
|
||||
epochData: Staking_nodes_epochData | null;
|
||||
status: NodeStatus;
|
||||
/**
|
||||
* Ranking scores and status for the validator for the current epoch
|
||||
*/
|
||||
rankingScore: Staking_nodes_rankingScore;
|
||||
}
|
||||
|
||||
export interface Staking_nodeData {
|
||||
|
204
apps/token/src/routes/staking/node-list.spec.tsx
Normal file
204
apps/token/src/routes/staking/node-list.spec.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { NodeList, NODES_QUERY } from './node-list';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type { Nodes_nodes } from './__generated__/Nodes';
|
||||
|
||||
jest.mock('../../components/epoch-countdown', () => ({
|
||||
EpochCountdown: () => <div data-testid="epoch-info"></div>,
|
||||
}));
|
||||
|
||||
const nodeFactory = (overrides?: Partial<Nodes_nodes>) => ({
|
||||
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
|
||||
name: 'Skynet',
|
||||
pubkey: '6abc23391a9f888ab240415bf63d6844b03fc360be822f4a1d2cd832d87b2917',
|
||||
infoUrl: 'https://en.wikipedia.org/wiki/Skynet_(Terminator)',
|
||||
location: '',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '11182454495731682635157',
|
||||
stakedTotal: '14182454495731682635157',
|
||||
pendingStake: '0',
|
||||
stakedByOperatorFormatted: addDecimal(
|
||||
overrides?.stakedByOperator || '3000000000000000000000',
|
||||
18
|
||||
),
|
||||
stakedByDelegatesFormatted: addDecimal(
|
||||
overrides?.stakedByDelegates || '11182454495731682635157',
|
||||
18
|
||||
),
|
||||
stakedTotalFormatted: addDecimal(
|
||||
overrides?.stakedTotal || '14182454495731682635157',
|
||||
18
|
||||
),
|
||||
pendingStakeFormatted: addDecimal(overrides?.pendingStake || '0', 18),
|
||||
epochData: null,
|
||||
status: 'Validator',
|
||||
rankingScore: {
|
||||
rankingScore: '0.67845061012234727427532760837568',
|
||||
stakeScore: '0.3392701644525644',
|
||||
performanceScore: '0.9998677767864936',
|
||||
votingPower: '2407',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const MOCK_NODES = {
|
||||
nodes: [
|
||||
nodeFactory(),
|
||||
nodeFactory({
|
||||
id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99',
|
||||
name: 'T-800 Terminator',
|
||||
pubkey:
|
||||
'ccc3b8362c25b09d20df8ea407b0a476d6b24a0e72bc063d0033c8841652ddd4',
|
||||
infoUrl: 'https://en.wikipedia.org/wiki/Terminator_(character)',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '6618711883996159534058',
|
||||
stakedTotal: '9618711883996159534058',
|
||||
rankingScore: {
|
||||
rankingScore: '0.4601942440481428',
|
||||
stakeScore: '0.2300971220240714',
|
||||
performanceScore: '1',
|
||||
votingPower: '2408',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
}),
|
||||
nodeFactory({
|
||||
id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81',
|
||||
name: 'NCC-1701-E',
|
||||
pubkey:
|
||||
'0931a8fd8cc935458f470e435a05414387cea6f329d648be894fcd44bd517a2b',
|
||||
infoUrl: 'https://en.wikipedia.org/wiki/USS_Enterprise_(NCC-1701-E)',
|
||||
stakedByOperator: '3000000000000000000000',
|
||||
stakedByDelegates: '1041343338923442976709',
|
||||
stakedTotal: '4041343338923442976709',
|
||||
pendingStake: '0',
|
||||
rankingScore: {
|
||||
rankingScore: '0.1932810100133910357676209647912',
|
||||
stakeScore: '0.0966762995515676',
|
||||
performanceScore: '0.999629748500531',
|
||||
votingPower: '1163',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
}),
|
||||
],
|
||||
nodeData: {
|
||||
stakedTotal: '27842509718651285145924',
|
||||
stakedTotalFormatted: addDecimal('27842509718651285145924', 18),
|
||||
totalNodes: 3,
|
||||
inactiveNodes: 0,
|
||||
validatingNodes: 3,
|
||||
uptime: 1560.266845703125,
|
||||
__typename: 'NodeData',
|
||||
},
|
||||
};
|
||||
|
||||
const renderNodeList = () => {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[
|
||||
{
|
||||
request: { query: NODES_QUERY },
|
||||
result: { data: MOCK_NODES },
|
||||
},
|
||||
]}
|
||||
>
|
||||
<NodeList
|
||||
epoch={{
|
||||
__typename: 'Epoch',
|
||||
id: '1',
|
||||
timestamps: {
|
||||
__typename: 'EpochTimestamps',
|
||||
start: new Date(0).toISOString(),
|
||||
end: '',
|
||||
expiry: new Date(1000 * 60 * 60 * 24).toISOString(),
|
||||
},
|
||||
}}
|
||||
party={{
|
||||
__typename: 'Party',
|
||||
id: 'foo',
|
||||
delegations: [
|
||||
{
|
||||
__typename: 'Delegation',
|
||||
amount: '0',
|
||||
amountFormatted: '0',
|
||||
epoch: 1,
|
||||
node: {
|
||||
__typename: 'Node',
|
||||
id: 'bar',
|
||||
},
|
||||
},
|
||||
],
|
||||
stake: {
|
||||
__typename: 'PartyStake',
|
||||
currentStakeAvailable: '0',
|
||||
currentStakeAvailableFormatted: '0',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('Nodes list', () => {
|
||||
it('should render epoch info', async () => {
|
||||
renderNodeList();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(MOCK_NODES.nodes[0].name)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('epoch-info')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should a lit of all nodes', async () => {
|
||||
renderNodeList();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(MOCK_NODES.nodes[0].name)).toBeInTheDocument();
|
||||
});
|
||||
const items = screen.queryAllByTestId('node-list-item');
|
||||
expect(items).toHaveLength(3);
|
||||
for (const item of MOCK_NODES.nodes) {
|
||||
expect(screen.getByText(item.name)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it('should list the total stake and rewards of each node', async () => {
|
||||
renderNodeList();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(MOCK_NODES.nodes[0].name)).toBeInTheDocument();
|
||||
});
|
||||
const items = screen.queryAllByTestId('node-list-item');
|
||||
const item = within(items[0]);
|
||||
const rows = item.getAllByRole('row');
|
||||
|
||||
const expectedValues = [
|
||||
['Total stake', '14,182.45 (50.94%)'],
|
||||
['Ranking score', '0.6785'],
|
||||
['Stake score', '0.3393'],
|
||||
['Performance score', '0.9999'],
|
||||
['Voting score', '2,407.0000'],
|
||||
];
|
||||
|
||||
for (const [i, r] of rows.entries()) {
|
||||
const row = within(r);
|
||||
const cell = row.getByRole('cell');
|
||||
const header = row.getByRole('rowheader');
|
||||
expect(header).toHaveTextContent(expectedValues[i][0]);
|
||||
expect(cell).toHaveTextContent(expectedValues[i][1]);
|
||||
}
|
||||
});
|
||||
});
|
@ -8,7 +8,7 @@ import { EpochCountdown } from '../../components/epoch-countdown';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { formatNumber } from '../../lib/format-number';
|
||||
import { truncateMiddle } from '../../lib/truncate-middle';
|
||||
import type { Nodes } from './__generated__/Nodes';
|
||||
import type { Nodes, Nodes_nodes_rankingScore } from './__generated__/Nodes';
|
||||
import type { Staking_epoch, Staking_party } from './__generated__/Staking';
|
||||
|
||||
export const NODES_QUERY = gql`
|
||||
@ -33,6 +33,13 @@ export const NODES_QUERY = gql`
|
||||
online
|
||||
}
|
||||
status
|
||||
rankingScore {
|
||||
rankingScore
|
||||
stakeScore
|
||||
performanceScore
|
||||
votingPower
|
||||
stakeScore
|
||||
}
|
||||
}
|
||||
nodeData {
|
||||
stakedTotal
|
||||
@ -54,7 +61,10 @@ const NodeListTr = ({ children }: { children: React.ReactNode }) => (
|
||||
);
|
||||
|
||||
const NodeListTh = ({ children }: { children: React.ReactNode }) => (
|
||||
<th className="flex-1 break-words py-1 pr-4 pl-0 text-white-60 font-normal">
|
||||
<th
|
||||
role="rowheader"
|
||||
className="flex-1 break-words py-1 pr-4 pl-0 text-white-60 font-normal"
|
||||
>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
@ -113,6 +123,7 @@ export const NodeList = ({ epoch, party }: NodeListProps) => {
|
||||
userStake,
|
||||
userStakePercentage,
|
||||
epoch,
|
||||
scores: node.rankingScore,
|
||||
};
|
||||
});
|
||||
|
||||
@ -160,6 +171,7 @@ export interface NodeListItemProps {
|
||||
stakedTotalPercentage: string;
|
||||
userStake: BigNumber;
|
||||
userStakePercentage: string;
|
||||
scores: Nodes_nodes_rankingScore;
|
||||
}
|
||||
|
||||
export const NodeListItem = ({
|
||||
@ -169,6 +181,7 @@ export const NodeListItem = ({
|
||||
stakedTotalPercentage,
|
||||
userStake,
|
||||
userStakePercentage,
|
||||
scores,
|
||||
}: NodeListItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -186,24 +199,36 @@ export const NodeListItem = ({
|
||||
<span
|
||||
className="uppercase text-white-60"
|
||||
title={`${t('id')}: ${id}`}
|
||||
data-testid="node-list-item-name"
|
||||
>
|
||||
{truncateMiddle(id)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
<table className="flex-1 text-body border-collapse mt-4">
|
||||
<table
|
||||
className="flex-1 text-body border-collapse mt-4"
|
||||
data-testid="node-list-item-table"
|
||||
>
|
||||
<tbody>
|
||||
<NodeListTr>
|
||||
<NodeListTh>{t('Total stake')}</NodeListTh>
|
||||
<NodeListTd>{formatNumber(stakedOnNode, 2)}</NodeListTd>
|
||||
<NodeListTd>{stakedTotalPercentage}</NodeListTd>
|
||||
</NodeListTr>
|
||||
<NodeListTr>
|
||||
<NodeListTh>{t('Your stake')}</NodeListTh>
|
||||
<NodeListTd>{formatNumber(userStake, 2)}</NodeListTd>
|
||||
<NodeListTd>{userStakePercentage}</NodeListTd>
|
||||
<NodeListTd>
|
||||
{formatNumber(stakedOnNode, 2)} ({stakedTotalPercentage})
|
||||
</NodeListTd>
|
||||
</NodeListTr>
|
||||
{scores
|
||||
? Object.entries(scores)
|
||||
.filter(([key]) => key !== '__typename')
|
||||
.map(([key, value]) => (
|
||||
<NodeListTr key={`${id}_${key}`}>
|
||||
<NodeListTh>{t(key)}</NodeListTh>
|
||||
<NodeListTd>
|
||||
{formatNumber(new BigNumber(value), 4)}
|
||||
</NodeListTd>
|
||||
</NodeListTr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
|
@ -53,6 +53,13 @@ export const STAKING_QUERY = gql`
|
||||
online
|
||||
}
|
||||
status
|
||||
rankingScore {
|
||||
rankingScore
|
||||
stakeScore
|
||||
performanceScore
|
||||
votingPower
|
||||
stakeScore
|
||||
}
|
||||
}
|
||||
nodeData {
|
||||
stakedTotal
|
||||
|
@ -22,14 +22,14 @@ export interface OrderEvent_busEvents_event_Order_market {
|
||||
/**
|
||||
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||
* number denominated in the currency of the Market. (uint64)
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
* Currency Balance decimalPlaces Real Balance
|
||||
* GBP 100 0 GBP 100
|
||||
* GBP 100 2 GBP 1.00
|
||||
* GBP 100 4 GBP 0.01
|
||||
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||
*
|
||||
*
|
||||
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||
|
Loading…
Reference in New Issue
Block a user