From c7e65080b7f2828b897e50a054cb35bb9f7f8a1e Mon Sep 17 00:00:00 2001 From: Dexter Edwards Date: Thu, 7 Jul 2022 13:37:41 +0100 Subject: [PATCH] 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 --- apps/token/src/i18n/translations/dev.json | 6 +- .../src/routes/staking/__generated__/Nodes.ts | 24 +++ .../routes/staking/__generated__/Staking.ts | 24 +++ .../src/routes/staking/node-list.spec.tsx | 204 ++++++++++++++++++ apps/token/src/routes/staking/node-list.tsx | 45 +++- .../staking/staking-nodes-container.tsx | 7 + .../order-hooks/__generated__/OrderEvent.ts | 4 +- 7 files changed, 301 insertions(+), 13 deletions(-) create mode 100644 apps/token/src/routes/staking/node-list.spec.tsx diff --git a/apps/token/src/i18n/translations/dev.json b/apps/token/src/i18n/translations/dev.json index 6f6463837..2d434dae2 100644 --- a/apps/token/src/i18n/translations/dev.json +++ b/apps/token/src/i18n/translations/dev.json @@ -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" } diff --git a/apps/token/src/routes/staking/__generated__/Nodes.ts b/apps/token/src/routes/staking/__generated__/Nodes.ts index e700cc6a0..7380837d4 100644 --- a/apps/token/src/routes/staking/__generated__/Nodes.ts +++ b/apps/token/src/routes/staking/__generated__/Nodes.ts @@ -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 { diff --git a/apps/token/src/routes/staking/__generated__/Staking.ts b/apps/token/src/routes/staking/__generated__/Staking.ts index eaf20ace7..b4460ea44 100644 --- a/apps/token/src/routes/staking/__generated__/Staking.ts +++ b/apps/token/src/routes/staking/__generated__/Staking.ts @@ -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 { diff --git a/apps/token/src/routes/staking/node-list.spec.tsx b/apps/token/src/routes/staking/node-list.spec.tsx new file mode 100644 index 000000000..9b2849cc9 --- /dev/null +++ b/apps/token/src/routes/staking/node-list.spec.tsx @@ -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: () =>
, +})); + +const nodeFactory = (overrides?: Partial) => ({ + 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( + + + + + + ); +}; + +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]); + } + }); +}); diff --git a/apps/token/src/routes/staking/node-list.tsx b/apps/token/src/routes/staking/node-list.tsx index 45efa7f55..321b3b553 100644 --- a/apps/token/src/routes/staking/node-list.tsx +++ b/apps/token/src/routes/staking/node-list.tsx @@ -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 }) => ( - + {children} ); @@ -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 = ({ {truncateMiddle(id)} )} - +
{t('Total stake')} - {formatNumber(stakedOnNode, 2)} - {stakedTotalPercentage} - - - {t('Your stake')} - {formatNumber(userStake, 2)} - {userStakePercentage} + + {formatNumber(stakedOnNode, 2)} ({stakedTotalPercentage}) + + {scores + ? Object.entries(scores) + .filter(([key]) => key !== '__typename') + .map(([key, value]) => ( + + {t(key)} + + {formatNumber(new BigNumber(value), 4)} + + + )) + : null}
diff --git a/apps/token/src/routes/staking/staking-nodes-container.tsx b/apps/token/src/routes/staking/staking-nodes-container.tsx index a6e0cfbd0..4986c6cf8 100644 --- a/apps/token/src/routes/staking/staking-nodes-container.tsx +++ b/apps/token/src/routes/staking/staking-nodes-container.tsx @@ -53,6 +53,13 @@ export const STAKING_QUERY = gql` online } status + rankingScore { + rankingScore + stakeScore + performanceScore + votingPower + stakeScore + } } nodeData { stakedTotal diff --git a/libs/wallet/src/order-hooks/__generated__/OrderEvent.ts b/libs/wallet/src/order-hooks/__generated__/OrderEvent.ts index 8ce11083c..a2d305009 100644 --- a/libs/wallet/src/order-hooks/__generated__/OrderEvent.ts +++ b/libs/wallet/src/order-hooks/__generated__/OrderEvent.ts @@ -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 )