From 0089920d4cdf7a696c133f11708d966edb1149a8 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Fri, 16 Jun 2023 10:04:03 +0100 Subject: [PATCH] fix(governance): earned by me reward percentage (#4093) --- .../epoch-individual-rewards.tsx | 9 +- ...rate-epoch-individual-rewards-list.spec.ts | 102 +++++++++++++++++- .../generate-epoch-individual-rewards-list.ts | 38 +++++-- .../src/routes/rewards/home/Rewards.graphql | 24 +++-- .../rewards/home/__generated__/Rewards.ts | 19 +++- 5 files changed, 165 insertions(+), 27 deletions(-) diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx b/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx index e70016de7..ef4b35a2f 100644 --- a/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx +++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx @@ -48,15 +48,22 @@ export const EpochIndividualRewards = ({ return removePaginationWrapper(data.party.rewardsConnection.edges); }, [data]); + const epochRewardSummaries = useMemo(() => { + if (!data?.epochRewardSummaries) return []; + + return removePaginationWrapper(data.epochRewardSummaries.edges); + }, [data]); + const epochIndividualRewardSummaries = useMemo(() => { if (!data?.party) return []; return generateEpochIndividualRewardsList({ rewards, epochId, + epochRewardSummaries, page, size: EPOCHS_PAGE_SIZE, }); - }, [data?.party, epochId, page, rewards]); + }, [data?.party, epochId, epochRewardSummaries, page, rewards]); const refetchData = useCallback( async (toPage?: number) => { diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts index 761f69bd5..e97c6e40b 100644 --- a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts +++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts @@ -65,7 +65,11 @@ describe('generateEpochIndividualRewardsList', () => { it('should return an empty array if no rewards are provided', () => { expect( - generateEpochIndividualRewardsList({ rewards: [], epochId: 1 }) + generateEpochIndividualRewardsList({ + rewards: [], + epochId: 1, + epochRewardSummaries: [], + }) ).toEqual([ { epoch: 1, @@ -78,6 +82,7 @@ describe('generateEpochIndividualRewardsList', () => { const result = generateEpochIndividualRewardsList({ rewards: [rewardWrongType], epochId: 1, + epochRewardSummaries: [], }); expect(result).toEqual([ @@ -92,6 +97,15 @@ describe('generateEpochIndividualRewardsList', () => { const result = generateEpochIndividualRewardsList({ rewards: [reward1], epochId: 1, + epochRewardSummaries: [ + { + __typename: 'EpochRewardSummary', + epoch: 1, + assetId: 'usd', + amount: '100000', + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + }, + ], }); expect(result[0]).toEqual({ @@ -134,7 +148,11 @@ describe('generateEpochIndividualRewardsList', () => { it('should return an array sorted by epoch descending', () => { const rewards = [reward1, reward2, reward3, reward4]; - const result1 = generateEpochIndividualRewardsList({ rewards, epochId: 2 }); + const result1 = generateEpochIndividualRewardsList({ + rewards, + epochId: 2, + epochRewardSummaries: [], + }); expect(result1[0].epoch).toEqual(2); expect(result1[1].epoch).toEqual(1); @@ -143,6 +161,7 @@ describe('generateEpochIndividualRewardsList', () => { const result2 = generateEpochIndividualRewardsList({ rewards: reorderedRewards, epochId: 2, + epochRewardSummaries: [], }); expect(result2[0].epoch).toEqual(2); @@ -151,7 +170,11 @@ describe('generateEpochIndividualRewardsList', () => { it('correctly calculates the total value of rewards for an asset', () => { const rewards = [reward1, reward4]; - const result = generateEpochIndividualRewardsList({ rewards, epochId: 1 }); + const result = generateEpochIndividualRewardsList({ + rewards, + epochId: 1, + epochRewardSummaries: [], + }); expect(result[0].rewards[0].totalAmount).toEqual('200'); }); @@ -159,7 +182,11 @@ describe('generateEpochIndividualRewardsList', () => { it('returns data in the expected shape', () => { // Just sanity checking the whole structure here const rewards = [reward1, reward2, reward3, reward4]; - const result = generateEpochIndividualRewardsList({ rewards, epochId: 2 }); + const result = generateEpochIndividualRewardsList({ + rewards, + epochId: 2, + epochRewardSummaries: [], + }); expect(result).toEqual([ { @@ -273,6 +300,7 @@ describe('generateEpochIndividualRewardsList', () => { const resultPageOne = generateEpochIndividualRewardsList({ rewards, epochId: 3, + epochRewardSummaries: [], page: 1, size: 2, }); @@ -386,6 +414,7 @@ describe('generateEpochIndividualRewardsList', () => { const resultPageTwo = generateEpochIndividualRewardsList({ rewards, epochId: 3, + epochRewardSummaries: [], page: 2, size: 2, }); @@ -429,4 +458,69 @@ describe('generateEpochIndividualRewardsList', () => { }, ]); }); + + it('correctly calculates the percentage of two or more rewards by referencing the total rewards amount', () => { + const result = generateEpochIndividualRewardsList({ + rewards: [ + // reward1 is 100 usd, which is 10% of the total rewards amount + reward1, + { + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + amount: '200', + percentageOfTotal: '0.2', + receivedAt: new Date(), + asset: { id: 'usd', symbol: 'USD', name: 'USD', decimals: 6 }, + party: { id: 'blah' }, + epoch: { id: '1' }, + }, + ], + epochId: 1, + epochRewardSummaries: [ + { + __typename: 'EpochRewardSummary', + epoch: 1, + assetId: 'usd', + amount: '1000', + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + }, + ], + }); + + expect(result[0]).toEqual({ + epoch: 1, + rewards: [ + { + asset: 'USD', + decimals: 6, + totalAmount: '300', + rewardTypes: { + [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: { + amount: '300', + percentageOfTotal: '30', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: { + amount: '0', + percentageOfTotal: '0', + }, + }, + }, + ], + }); + }); }); diff --git a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts index 953595c5e..b6bf300c1 100644 --- a/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts +++ b/apps/governance/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts @@ -1,6 +1,9 @@ import { BigNumber } from '../../../lib/bignumber'; import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets'; -import type { RewardFieldsFragment } from '../home/__generated__/Rewards'; +import type { + EpochRewardSummaryFieldsFragment, + RewardFieldsFragment, +} from '../home/__generated__/Rewards'; import type { AccountType } from '@vegaprotocol/types'; import { calculateEpochOffset } from '../../../lib/epoch-pagination'; @@ -32,11 +35,13 @@ const emptyRowAccountTypes = accountTypes.map((type) => [ export const generateEpochIndividualRewardsList = ({ rewards, epochId, + epochRewardSummaries, page = 1, size = 10, }: { rewards: RewardFieldsFragment[]; epochId: number; + epochRewardSummaries: EpochRewardSummaryFieldsFragment[]; page?: number; size?: number; }) => { @@ -54,6 +59,7 @@ export const generateEpochIndividualRewardsList = ({ const epochIndividualRewards = rewards.reduce((acc, reward) => { const epochId = reward.epoch.id; const assetName = reward.asset.name; + const assetId = reward.asset.id; const assetDecimals = reward.asset.decimals; const rewardType = reward.rewardType; const amount = reward.amount; @@ -70,6 +76,14 @@ export const generateEpochIndividualRewardsList = ({ const epoch = acc.get(epochId); + // matchingTotalReward is the total awarded for all users for the reward type in the epoch of the asset + const matchingTotalRewardAmount = epochRewardSummaries.find( + (summary) => + summary.epoch === Number(epochId) && + summary.assetId === assetId && + summary.rewardType === rewardType + )?.amount; + let asset = epoch?.rewards.find((r) => r.asset === assetName); if (!asset) { @@ -86,22 +100,24 @@ export const generateEpochIndividualRewardsList = ({ asset.rewardTypes[rewardType] = { amount, percentageOfTotal }; } else { const previousAmount = asset.rewardTypes[rewardType]?.amount; - const previousPercentageOfTotal = - asset.rewardTypes[rewardType]?.percentageOfTotal; + const newAmount = previousAmount + ? new BigNumber(previousAmount).plus(amount).toString() + : amount; asset.rewardTypes[rewardType] = { - amount: previousAmount - ? new BigNumber(previousAmount).plus(amount).toString() - : amount, - percentageOfTotal: previousPercentageOfTotal - ? new BigNumber(previousPercentageOfTotal) - .plus(percentageOfTotal) + amount: newAmount, + percentageOfTotal: matchingTotalRewardAmount + ? new BigNumber(newAmount) + .dividedBy(matchingTotalRewardAmount) + .multipliedBy(100) .toString() - : percentageOfTotal, + : // this should never be reached, if there's an individual reward there should + // always be a reward total from the api too, but set it as a fallback just in case + percentageOfTotal, }; } - // totalAmount is the sum of all rewardTypes amounts + // totalAmount is the sum of all individual rewardTypes amounts asset.totalAmount = Object.values(asset.rewardTypes).reduce( (sum, rewardType) => { return new BigNumber(sum).plus(rewardType.amount).toString(); diff --git a/apps/governance/src/routes/rewards/home/Rewards.graphql b/apps/governance/src/routes/rewards/home/Rewards.graphql index 59a26c212..f4c05484e 100644 --- a/apps/governance/src/routes/rewards/home/Rewards.graphql +++ b/apps/governance/src/routes/rewards/home/Rewards.graphql @@ -22,6 +22,13 @@ fragment DelegationFields on Delegation { epoch } +fragment EpochRewardSummaryFields on EpochRewardSummary { + epoch + assetId + amount + rewardType +} + query Rewards( $partyId: ID! $fromEpoch: Int @@ -50,13 +57,16 @@ query Rewards( } } } -} - -fragment EpochRewardSummaryFields on EpochRewardSummary { - epoch - assetId - amount - rewardType + epochRewardSummaries( + filter: { fromEpoch: $fromEpoch, toEpoch: $toEpoch } + pagination: $rewardsPagination + ) { + edges { + node { + ...EpochRewardSummaryFields + } + } + } } query EpochAssetsRewards( diff --git a/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts b/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts index 8681a3200..4d2e49c6d 100644 --- a/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts +++ b/apps/governance/src/routes/rewards/home/__generated__/Rewards.ts @@ -7,6 +7,8 @@ export type RewardFieldsFragment = { __typename?: 'Reward', rewardType: Types.Ac export type DelegationFieldsFragment = { __typename?: 'Delegation', amount: string, epoch: number }; +export type EpochRewardSummaryFieldsFragment = { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType }; + export type RewardsQueryVariables = Types.Exact<{ partyId: Types.Scalars['ID']; fromEpoch?: Types.InputMaybe; @@ -16,9 +18,7 @@ export type RewardsQueryVariables = Types.Exact<{ }>; -export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardsConnection?: { __typename?: 'RewardsConnection', edges?: Array<{ __typename?: 'RewardEdge', node: { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } } | null> | null } | null, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number } } | null> | null } | null } | null }; - -export type EpochRewardSummaryFieldsFragment = { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType }; +export type RewardsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, rewardsConnection?: { __typename?: 'RewardsConnection', edges?: Array<{ __typename?: 'RewardEdge', node: { __typename?: 'Reward', rewardType: Types.AccountType, amount: string, percentageOfTotal: string, receivedAt: any, asset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string }, epoch: { __typename?: 'Epoch', id: string } } } | null> | null } | null, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number } } | null> | null } | null } | null, epochRewardSummaries?: { __typename?: 'EpochRewardSummaryConnection', edges?: Array<{ __typename?: 'EpochRewardSummaryEdge', node: { __typename?: 'EpochRewardSummary', epoch: number, assetId: string, amount: string, rewardType: Types.AccountType } } | null> | null } | null }; export type EpochAssetsRewardsQueryVariables = Types.Exact<{ epochRewardSummariesFilter?: Types.InputMaybe; @@ -102,9 +102,20 @@ export const RewardsDocument = gql` } } } + epochRewardSummaries( + filter: {fromEpoch: $fromEpoch, toEpoch: $toEpoch} + pagination: $rewardsPagination + ) { + edges { + node { + ...EpochRewardSummaryFields + } + } + } } ${RewardFieldsFragmentDoc} -${DelegationFieldsFragmentDoc}`; +${DelegationFieldsFragmentDoc} +${EpochRewardSummaryFieldsFragmentDoc}`; /** * __useRewardsQuery__