fix: use correct multiplier for vesting

This commit is contained in:
Matthew Russell
2023-11-14 21:55:36 -08:00
parent e8bc651f0e
commit 8175ed41b3
4 changed files with 86 additions and 126 deletions
@@ -2,9 +2,14 @@ query RewardsPage($partyId: ID!) {
party(id: $partyId) {
id
vestingStats {
epochSeq
# AKA hoarder reward multiplier
rewardBonusMultiplier
quantumBalance
}
activityStreak {
# vesting multiplier
rewardVestingMultiplier
# AKA streak multiplier
rewardDistributionMultiplier
}
vestingBalancesSummary {
epoch
@@ -8,7 +8,7 @@ export type RewardsPageQueryVariables = Types.Exact<{
}>;
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', epochSeq: number, rewardBonusMultiplier: string, quantumBalance: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string } | null, activityStreak?: { __typename?: 'PartyActivityStreak', rewardVestingMultiplier: string, rewardDistributionMultiplier: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
export type RewardsHistoryQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
@@ -32,9 +32,11 @@ export const RewardsPageDocument = gql`
party(id: $partyId) {
id
vestingStats {
epochSeq
rewardBonusMultiplier
quantumBalance
}
activityStreak {
rewardVestingMultiplier
rewardDistributionMultiplier
}
vestingBalancesSummary {
epoch
@@ -3,9 +3,6 @@ import type { Account } from '@vegaprotocol/accounts';
import { AccountType, AssetStatus } from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { RewardPot, Vesting, type RewardPotProps } from './rewards-container';
import type { MockedResponse } from '@apollo/react-testing';
import { MockedProvider } from '@apollo/react-testing';
import { type AssetQuery, AssetDocument } from '@vegaprotocol/assets';
const rewardAsset = {
id: 'asset-1',
@@ -154,65 +151,27 @@ describe('RewardPot', () => {
});
describe('Vesting', () => {
it('renders vesting rates', async () => {
const assetMock: MockedResponse<AssetQuery> = {
request: {
query: AssetDocument,
variables: {
assetId: rewardAsset.id,
},
},
result: {
data: {
assetsConnection: {
edges: [
{
node: rewardAsset,
},
],
},
},
},
};
render(
<MockedProvider mocks={[assetMock]}>
<Vesting
assetId={rewardAsset.id}
baseRate={'0.25'}
pubKey="pubKey"
multiplier="2"
vestingBalancesSummary={{
epoch: 5,
lockedBalances: [
{
balance: '150',
asset: rewardAsset,
untilEpoch: 6,
},
{
balance: '100',
asset: rewardAsset,
untilEpoch: 6,
},
{
balance: '100',
asset: rewardAsset,
untilEpoch: 7,
},
],
}}
epoch={5}
/>
</MockedProvider>
it('renders vesting rates', () => {
render(<Vesting baseRate={'0.25'} pubKey="pubKey" multiplier="2" />);
expect(screen.getByTestId('vesting-rate')).toHaveTextContent('50%');
expect(screen.getByText('Base rate').nextElementSibling).toHaveTextContent(
'25%'
);
expect(
screen.getByText('Vesting multiplier').nextSibling
).toHaveTextContent('2x');
});
const computedRate = await screen.findByTestId('vesting-rate');
expect(computedRate).toHaveTextContent('50%');
it('doesnt use multiplier if not connected', () => {
render(<Vesting baseRate={'0.25'} pubKey={null} multiplier={undefined} />);
const baseRateLabel = screen.getByText(/Base rate/);
expect(baseRateLabel.nextElementSibling).toHaveTextContent('25%');
expect(screen.getByTestId('vesting-rate')).toHaveTextContent('25%');
const nextEpochLabel = screen.getByText('Available to withdraw next epoch');
expect(nextEpochLabel.nextElementSibling).toHaveTextContent('2.50');
expect(screen.getByText('Base rate').nextElementSibling).toHaveTextContent(
'25%'
);
expect(screen.queryByText('Vesting multiplier')).not.toBeInTheDocument();
});
});
@@ -31,7 +31,6 @@ import { addDecimalsFormatNumberQuantum } from '@vegaprotocol/utils';
import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { RewardsHistoryContainer } from './rewards-history';
import { useAssetDataProvider } from '@vegaprotocol/assets';
export const RewardsContainer = () => {
const { pubKey } = useVegaWallet();
@@ -72,7 +71,7 @@ export const RewardsContainer = () => {
// TODO: Fix grid rows, they break on small screens when things stack
return (
<div className="grid auto-rows-min grid-cols-6 gap-3">
{/* Always show the rewards pot for the reward asset AKA Vega */}
{/* Always show reward information for vega */}
<Card
key={params.reward_asset}
title={t('Vega Reward pot')}
@@ -87,8 +86,32 @@ export const RewardsContainer = () => {
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
/>
</Card>
<Card title={t('Vesting')} className="lg:col-span-2" loading={loading}>
<Vesting
pubKey={pubKey}
baseRate={params.rewards_vesting_baseRate}
multiplier={
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
}
/>
</Card>
<Card
title={t('Rewards multipliers')}
className="lg:col-span-2"
loading={loading}
highlight={true}
>
<Multipliers
hoarderMultiplier={
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
}
streakMultiplier={
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
}
/>
</Card>
{/* Show all other rewards */}
{/* Show all other reward pots, most of the time users will not have other rewards */}
{Object.keys(rewardAssetsMap).map((assetId) => {
const asset = rewardAssetsMap[assetId][0].asset;
return (
@@ -109,23 +132,6 @@ export const RewardsContainer = () => {
</Card>
);
})}
<Card title={t('Vesting')} className="lg:col-span-2" loading={loading}>
<Vesting
pubKey={pubKey}
assetId={params.reward_asset}
baseRate={params.rewards_vesting_baseRate}
multiplier={rewardsData?.party?.vestingStats?.rewardBonusMultiplier}
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
epoch={Number(epochData.epoch.id)}
/>
</Card>
<Card
title={t('Rewards multipliers')}
className="lg:col-span-2"
loading={loading}
>
<Multipliers />
</Card>
<Card
title={t('Rewards history')}
className="lg:col-span-full"
@@ -291,42 +297,18 @@ export const RewardPot = ({
};
export const Vesting = ({
assetId,
pubKey,
baseRate,
multiplier = '1',
vestingBalancesSummary,
epoch,
}: {
assetId: string;
pubKey: string | null;
baseRate: string;
multiplier?: string;
vestingBalancesSummary: VestingBalances | undefined;
epoch: number;
}) => {
const { data: asset } = useAssetDataProvider(assetId);
if (!asset) return null;
const rate = new BigNumber(baseRate).times(multiplier);
const rateFormatted = formatPercentage(Number(rate));
const baseRateFormatted = formatPercentage(Number(baseRate));
const lockedEntries = vestingBalancesSummary?.lockedBalances?.filter(
(b) => b.asset.id === assetId
);
const unlockingNextEpoch = lockedEntries?.filter(
(e) => e.untilEpoch === epoch + 1
);
const nextEpochBalances = unlockingNextEpoch?.length
? unlockingNextEpoch.map((e) => e.balance)
: [0];
const totalUnlockingNextEpoch = BigNumber.sum.apply(null, nextEpochBalances);
return (
<div className="pt-4">
<CardStat value={rateFormatted + '%'} testId={'vesting-rate'} />
@@ -336,32 +318,44 @@ export const Vesting = ({
<CardTableTD>{baseRateFormatted}%</CardTableTD>
</tr>
{pubKey && (
<>
<tr>
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
<CardTableTD>{multiplier}</CardTableTD>
</tr>
<tr>
<CardTableTH>{t('Available to withdraw next epoch')}</CardTableTH>
<CardTableTD>
{addDecimalsFormatNumberQuantum(
totalUnlockingNextEpoch.toString(),
asset.decimals,
asset.quantum
)}
</CardTableTD>
</tr>
</>
<tr>
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
<CardTableTD>{multiplier}x</CardTableTD>
</tr>
)}
</CardTable>
</div>
);
};
const Multipliers = () => {
const Multipliers = ({
streakMultiplier = '1',
hoarderMultiplier = '1',
}: {
streakMultiplier?: string;
hoarderMultiplier?: string;
}) => {
const combinedMultiplier = new BigNumber(streakMultiplier).times(
hoarderMultiplier
);
return (
<div className="pt-4">
<p className="text-muted text-sm">{t('No active reward bonuses')}</p>
<CardStat
value={combinedMultiplier.toString() + 'x'}
testId={'vesting-rate'}
highlight={true}
/>
<CardTable>
<tr>
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
<CardTableTD>{streakMultiplier}x</CardTableTD>
</tr>
<tr>
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
<CardTableTD>{hoarderMultiplier}x</CardTableTD>
</tr>
</CardTable>
</div>
);
};