Compare commits

...
14 changed files with 384 additions and 71 deletions
@@ -49,12 +49,8 @@ export const ContractsProvider = ({ children }: { children: JSX.Element }) => {
? activeProvider
: defaultProvider;
if (
account &&
activeProvider &&
typeof activeProvider.getSigner === 'function'
) {
signer = provider.getSigner();
if (account && provider && typeof provider.getSigner === 'function') {
signer = provider.getSigner(account);
}
const tokenVestingAddress =
@@ -54,8 +54,8 @@ export const ProposalReferralProgramDetails = ({
return null;
}
const benefitTiers = proposal?.terms?.change?.benefitTiers;
const stakingTiers = proposal?.terms?.change?.stakingTiers;
const benefitTiers = proposal?.terms?.change?.benefitTiers.slice();
const stakingTiers = proposal?.terms?.change?.stakingTiers.slice();
const windowLength = proposal?.terms?.change?.windowLength;
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
+4
View File
@@ -104,6 +104,10 @@
list-style: circle;
}
.react-markdown-container a {
text-decoration: underline;
}
.jsondiffpatch-delta,
.jsondiffpatch-delta pre {
font-family: 'Roboto Mono', monospace !important;
@@ -310,16 +310,25 @@ export const CurrentVolume = ({
const t = useT();
const nextTier = tiers[tierIndex + 1];
const requiredForNextTier = nextTier
? Number(nextTier.minimumRunningNotionalTakerVolume) - windowLengthVolume
: 0;
? new BigNumber(nextTier.minimumRunningNotionalTakerVolume).minus(
windowLengthVolume
)
: new BigNumber(0);
const currentVolume = new BigNumber(windowLengthVolume);
return (
<div className="flex flex-col gap-3 pt-4">
<CardStat
value={formatNumberRounded(new BigNumber(windowLengthVolume))}
text={t('pastEpochs', 'Past {{count}} epochs', { count: windowLength })}
value={
currentVolume.isZero()
? `<${formatNumberRounded(requiredForNextTier)}`
: formatNumberRounded(currentVolume)
}
text={t('pastEpochs', 'Past {{count}} epochs', {
count: windowLength,
})}
/>
{requiredForNextTier > 0 && (
{requiredForNextTier.isGreaterThan(0) && (
<CardStat
value={formatNumber(requiredForNextTier)}
text={t('Required for next tier')}
@@ -1,4 +1,5 @@
import groupBy from 'lodash/groupBy';
import uniq from 'lodash/uniq';
import type { Account } from '@vegaprotocol/accounts';
import { useAccounts } from '@vegaprotocol/accounts';
import {
@@ -31,6 +32,12 @@ import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { RewardsHistoryContainer } from './rewards-history';
import { useT } from '../../lib/use-t';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
const ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA = [
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
'8ba0b10971f0c4747746cd01ff05a53ae75ca91eba1d4d050b527910c983e27e', // USDT testnet
];
export const RewardsContainer = () => {
const t = useT();
@@ -40,34 +47,67 @@ export const RewardsContainer = () => {
NetworkParams.rewards_activityStreak_benefitTiers,
NetworkParams.rewards_vesting_baseRate,
]);
const { data: accounts, loading: accountsLoading } = useAccounts(pubKey);
const { data: assetMap } = useAssetsMapProvider();
const { data: epochData } = useRewardsEpochQuery();
// No need to specify the fromEpoch as it will by default give you the last
// Note activityStreak in query will fail
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
variables: {
partyId: pubKey || '',
},
// Inclusion of activity streak in query currently fails
errorPolicy: 'ignore',
});
if (!epochData?.epoch) return null;
if (!epochData?.epoch || !assetMap) return null;
const loading = paramsLoading || accountsLoading || rewardsLoading;
const rewardAccounts = accounts
? accounts.filter((a) =>
[
AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
AccountType.ACCOUNT_TYPE_VESTING_REWARDS,
].includes(a.type)
)
? accounts
.filter((a) =>
[
AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
AccountType.ACCOUNT_TYPE_VESTING_REWARDS,
].includes(a.type)
)
.filter((a) => new BigNumber(a.balance).isGreaterThan(0))
: [];
const rewardAssetsMap = groupBy(
rewardAccounts.filter((a) => a.asset.id !== params.reward_asset),
'asset.id'
);
const rewardAccountsAssetMap = groupBy(rewardAccounts, 'asset.id');
const lockedBalances = rewardsData?.party?.vestingBalancesSummary
.lockedBalances
? rewardsData.party.vestingBalancesSummary.lockedBalances.filter((b) =>
new BigNumber(b.balance).isGreaterThan(0)
)
: [];
const lockedAssetMap = groupBy(lockedBalances, 'asset.id');
const vestingBalances = rewardsData?.party?.vestingBalancesSummary
.vestingBalances
? rewardsData.party.vestingBalancesSummary.vestingBalances.filter((b) =>
new BigNumber(b.balance).isGreaterThan(0)
)
: [];
const vestingAssetMap = groupBy(vestingBalances, 'asset.id');
// each asset reward pot is made up of:
// available to withdraw - ACCOUNT_TYPE_VESTED_REWARDS
// vesting - vestingBalancesSummary.vestingBalances
// locked - vestingBalancesSummary.lockedBalances
//
// there can be entires for the same asset in each list so we need a uniq list of assets
const assets = uniq([
...Object.keys(rewardAccountsAssetMap),
...Object.keys(lockedAssetMap),
...Object.keys(vestingAssetMap),
]);
return (
<div className="grid auto-rows-min grid-cols-6 gap-3">
@@ -117,28 +157,72 @@ export const RewardsContainer = () => {
</Card>
{/* 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 (
<Card
key={assetId}
title={t('{{assetSymbol}} Reward pot', {
assetSymbol: asset.symbol,
})}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={assetId}
vestingBalancesSummary={
rewardsData?.party?.vestingBalancesSummary
}
/>
</Card>
);
})}
{assets
.filter((assetId) => assetId !== params.reward_asset)
.map((assetId) => {
const asset = assetMap ? assetMap[assetId] : null;
if (!asset) return null;
// Following code is for mitigating an issue due to a core bug where locked and vesting
// balances were incorrectly increased for infrastructure rewards for USDT on mainnet
//
// We don't want to incorrectly show the wring locked/vesting values, but we DO want to
// show the user that they have rewards available to withdraw
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
const accountsForAsset = rewardAccountsAssetMap[asset.id];
const vestedAccount = accountsForAsset?.find(
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
// No vested rewards available to withdraw, so skip over USDT
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
return null;
}
return (
<Card
key={assetId}
title={t('{{assetSymbol}} Reward pot', {
assetSymbol: asset.symbol,
})}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={assetId}
// Ensure that these values are shown as 0
vestingBalancesSummary={{
lockedBalances: [],
vestingBalances: [],
}}
/>
</Card>
);
}
return (
<Card
key={assetId}
title={t('{{assetSymbol}} Reward pot', {
assetSymbol: asset.symbol,
})}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={assetId}
vestingBalancesSummary={
rewardsData?.party?.vestingBalancesSummary
}
/>
</Card>
);
})}
<Card
title={t('Rewards history')}
className="lg:col-span-full"
@@ -147,6 +231,7 @@ export const RewardsContainer = () => {
<RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)}
pubKey={pubKey}
assets={assetMap}
/>
</Card>
</div>
@@ -313,14 +398,14 @@ export const RewardPot = ({
export const Vesting = ({
pubKey,
baseRate,
multiplier = '1',
multiplier,
}: {
pubKey: string | null;
baseRate: string;
multiplier?: string;
}) => {
const t = useT();
const rate = new BigNumber(baseRate).times(multiplier);
const rate = new BigNumber(baseRate).times(multiplier || 1);
const rateFormatted = formatPercentage(Number(rate));
const baseRateFormatted = formatPercentage(Number(baseRate));
@@ -335,7 +420,7 @@ export const Vesting = ({
{pubKey && (
<tr>
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
<CardTableTD>{multiplier}x</CardTableTD>
<CardTableTD>{multiplier ? `${multiplier}x` : '-'}</CardTableTD>
</tr>
)}
</CardTable>
@@ -345,16 +430,16 @@ export const Vesting = ({
export const Multipliers = ({
pubKey,
streakMultiplier = '1',
hoarderMultiplier = '1',
streakMultiplier,
hoarderMultiplier,
}: {
pubKey: string | null;
streakMultiplier?: string;
hoarderMultiplier?: string;
}) => {
const t = useT();
const combinedMultiplier = new BigNumber(streakMultiplier).times(
hoarderMultiplier
const combinedMultiplier = new BigNumber(streakMultiplier || 1).times(
hoarderMultiplier || 1
);
if (!pubKey) {
@@ -375,11 +460,15 @@ export const Multipliers = ({
<CardTable>
<tr>
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
<CardTableTD>{streakMultiplier}x</CardTableTD>
<CardTableTD>
{streakMultiplier ? `${streakMultiplier}x` : '-'}
</CardTableTD>
</tr>
<tr>
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
<CardTableTD>{hoarderMultiplier}x</CardTableTD>
<CardTableTD>
{hoarderMultiplier ? `${hoarderMultiplier}x` : '-'}
</CardTableTD>
</tr>
</CardTable>
</div>
@@ -61,6 +61,14 @@ const rewardSummaries = [
rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS,
},
},
{
node: {
epoch: 7,
assetId: assets.asset2.id,
amount: '300',
rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
},
},
];
const getCell = (cells: HTMLElement[], colId: string) => {
@@ -69,7 +77,7 @@ const getCell = (cells: HTMLElement[], colId: string) => {
);
};
describe('RewarsHistoryTable', () => {
describe('RewardsHistoryTable', () => {
const props = {
epochRewardSummaries: {
edges: rewardSummaries,
@@ -88,7 +96,7 @@ describe('RewarsHistoryTable', () => {
loading: false,
};
it('Renders table with accounts summed up by asset', () => {
it('renders table with accounts summed up by asset', () => {
render(<RewardHistoryTable {...props} />);
const container = within(
@@ -110,17 +118,27 @@ describe('RewarsHistoryTable', () => {
assets.asset2.name
);
// First row
const marketCreationCell = getCell(cells, 'marketCreation');
expect(
marketCreationCell.getByTestId('stack-cell-primary')
).toHaveTextContent('300');
expect(
marketCreationCell.getByTestId('stack-cell-secondary')
).toHaveTextContent('100.00%');
).toHaveTextContent('50.00%');
const infrastructureFeesCell = getCell(cells, 'infrastructureFees');
expect(
infrastructureFeesCell.getByTestId('stack-cell-primary')
).toHaveTextContent('300');
expect(
infrastructureFeesCell.getByTestId('stack-cell-secondary')
).toHaveTextContent('50.00%');
let totalCell = getCell(cells, 'total');
expect(totalCell.getByText('300.00')).toBeInTheDocument();
expect(totalCell.getByText('600.00')).toBeInTheDocument();
// Second row
row = within(rows[1]);
cells = row.getAllByRole('gridcell');
@@ -2,10 +2,7 @@ import debounce from 'lodash/debounce';
import { useMemo, useState } from 'react';
import BigNumber from 'bignumber.js';
import type { ColDef, ValueFormatterFunc } from 'ag-grid-community';
import {
useAssetsMapProvider,
type AssetFieldsFragment,
} from '@vegaprotocol/assets';
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
import {
addDecimalsFormatNumberQuantum,
formatNumberPercentage,
@@ -26,17 +23,17 @@ import { useT } from '../../lib/use-t';
export const RewardsHistoryContainer = ({
epoch,
pubKey,
assets,
}: {
pubKey: string | null;
epoch: number;
assets: Record<string, AssetFieldsFragment>;
}) => {
const [epochVariables, setEpochVariables] = useState(() => ({
from: epoch - 1,
to: epoch,
}));
const { data: assets } = useAssetsMapProvider();
// No need to specify the fromEpoch as it will by default give you the last
const { refetch, data, loading } = useRewardsHistoryQuery({
variables: {
@@ -154,10 +151,12 @@ export const RewardHistoryTable = ({
const rewardValueFormatter: ValueFormatterFunc<RewardRow> = ({
data,
value,
...rest
}) => {
if (!value || !data) {
return '-';
}
return addDecimalsFormatNumberQuantum(
value,
data.asset.decimals,
@@ -197,6 +196,11 @@ export const RewardHistoryTable = ({
},
sort: 'desc',
},
{
field: 'infrastructureFees',
valueFormatter: rewardValueFormatter,
cellRenderer: rewardCellRenderer,
},
{
field: 'staking',
valueFormatter: rewardValueFormatter,
@@ -0,0 +1,159 @@
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
import { getRewards } from './use-reward-row-data';
import * as Schema from '@vegaprotocol/types';
const asset1 = {
id: 'asset1',
name: 'USD (KRW)',
symbol: 'USD-KRW',
decimals: 6,
quantum: '1000000',
status: Schema.AssetStatus.STATUS_ENABLED,
// @ts-ignore not needed
source: {},
} as AssetFieldsFragment;
const asset2 = {
id: 'asset2',
name: 'tDAI TEST',
symbol: 'tDAI',
decimals: 5,
quantum: '1',
status: Schema.AssetStatus.STATUS_ENABLED,
// @ts-ignore not needed
source: {},
} as AssetFieldsFragment;
const asset3 = {
id: 'asset3',
name: 'Tether USD',
symbol: 'USDT',
decimals: 6,
quantum: '1000000',
status: Schema.AssetStatus.STATUS_ENABLED,
// @ts-ignore not needed
source: {},
} as AssetFieldsFragment;
const asset4 = {
id: 'asset4',
name: 'USDT-T',
symbol: 'USDT-T',
decimals: 18,
quantum: '1',
status: Schema.AssetStatus.STATUS_ENABLED,
// @ts-ignore not needed
source: {},
} as AssetFieldsFragment;
const assets: Record<string, AssetFieldsFragment> = {
asset1,
asset2,
asset3,
asset4,
};
const testData = {
rewards: [
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
assetId: 'asset1',
amount: '31897424',
},
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
assetId: 'asset2',
amount: '57',
},
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
assetId: 'asset3',
amount: '5501',
},
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
assetId: 'asset3',
amount: '5501',
},
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES,
assetId: 'asset4',
amount: '5501',
},
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
assetId: 'asset4',
amount: '456',
},
{
rewardType: Schema.AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING,
assetId: 'asset4',
amount: '4565',
},
],
assets,
};
describe('getRewards', () => {
it('should return the correct rewards when infra fees are included', () => {
const rewards = getRewards(testData.rewards, testData.assets);
expect(rewards).toEqual([
{
asset: asset1,
infrastructureFees: 31897424,
staking: 0,
priceTaking: 0,
priceMaking: 0,
liquidityProvision: 0,
marketCreation: 0,
averagePosition: 0,
relativeReturns: 0,
returnsVolatility: 0,
validatorRanking: 0,
total: 31897424,
},
{
asset: asset2,
infrastructureFees: 57,
staking: 0,
priceTaking: 0,
priceMaking: 0,
liquidityProvision: 0,
marketCreation: 0,
averagePosition: 0,
relativeReturns: 0,
returnsVolatility: 0,
validatorRanking: 0,
total: 57,
},
{
asset: asset3,
infrastructureFees: 5501,
staking: 0,
priceTaking: 0,
priceMaking: 0,
liquidityProvision: 0,
marketCreation: 0,
averagePosition: 5501,
relativeReturns: 0,
returnsVolatility: 0,
validatorRanking: 0,
total: 11002,
},
{
asset: asset4,
infrastructureFees: 0,
staking: 0,
priceTaking: 0,
priceMaking: 5501,
liquidityProvision: 456,
marketCreation: 0,
averagePosition: 0,
relativeReturns: 0,
returnsVolatility: 0,
validatorRanking: 4565,
total: 10522,
},
]);
});
});
@@ -16,9 +16,10 @@ const REWARD_ACCOUNT_TYPES = [
AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN,
AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY,
AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING,
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
];
const getRewards = (
export const getRewards = (
rewards: Array<{
rewardType: AccountType;
assetId: string;
@@ -56,6 +57,9 @@ const getRewards = (
return {
asset,
infrastructureFees: totals.get(
AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE
),
staking: totals.get(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD),
priceTaking: totals.get(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES),
priceMaking: totals.get(
@@ -101,7 +105,8 @@ export const useRewardsRowData = ({
assetId: r.asset.id,
amount: r.amount,
}));
return getRewards(rewards, assets);
const result = getRewards(rewards, assets);
return result;
}
const rewards = removePaginationWrapper(epochRewardSummaries?.edges);
@@ -2,6 +2,7 @@ import pytest
from playwright.sync_api import Page, expect
from vega_sim.service import VegaService
from actions.vega import submit_order
from actions.utils import change_keys
from wallet_config import MM_WALLET, MM_WALLET2
import logging
@@ -196,3 +197,17 @@ def test_price_monitoring(simple_market, vega: VegaService, page: Page):
expect(
page.get_by_test_id(liquidity_supplied).get_by_test_id(item_value)
).to_have_text("50.00 (>100%)")
COL_ID_FEE = ".ag-center-cols-container [col-id='fee'] .ag-cell-value"
@pytest.mark.usefixtures("vega", "page", "continuous_market", "risk_accepted", "auth")
def test_auction_uncross_fees(continuous_market, vega: VegaService, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Fills").click()
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text("If the market was suspendedIf the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI")
change_keys(page,vega, "market_maker")
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text("If the market was suspendedIf the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI")
@@ -5,6 +5,7 @@ from playwright.sync_api import Page, expect
from vega_sim.service import VegaService, PeggedOrder
import vega_sim.api.governance as governance
from actions.vega import submit_order
from actions.utils import next_epoch
from wallet_config import MM_WALLET, MM_WALLET2, GOVERNANCE_WALLET
@@ -58,9 +59,9 @@ def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
# "wait" for market to be approved and enacted
vega.forward("60s")
vega.wait_fn(1)
vega.wait_fn(10)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
# check that market is in pending state
expect(trading_mode).to_have_text("Opening auction")
expect(market_state).to_have_text("Pending")
@@ -118,7 +118,6 @@ def test_perps_market_termination_proposed(page: Page, vega: VegaService):
@pytest.mark.usefixtures("page","risk_accepted", "auth" )
def test_perps_market_terminated(page: Page, vega: VegaService):
perpetual_market = setup_perps_market(vega)
page.goto(f"/#/markets/{perpetual_market}")
vega.update_market_state(
proposal_key=MM_WALLET.name,
market_id=perpetual_market,
@@ -127,6 +126,11 @@ def test_perps_market_terminated(page: Page, vega: VegaService):
approve_proposal = True,
forward_time_to_enactment = True,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.goto(f"/#/markets/{perpetual_market}")
expect(page.get_by_test_id("market-price")).to_have_text("Mark Price100.00")
expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)-")
expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)-")
@@ -119,6 +119,8 @@ describe('getLiquidityProvision', () => {
createdAt: '2022-12-16T09:28:29.071781Z',
id: 'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
fee: '0.001',
partyId:
'dde288688af2aeb5feb349dd72d3679a7a9be34c7375f6a4a48ef2f6140e7e59',
party: {
__typename: 'Party',
accountsConnection: {
@@ -159,7 +159,14 @@ export const getLiquidityProvision = (
const liquidityProvider = liquidityProviders.find(
(f) => liquidityProvision.party.id === f.partyId
);
if (!liquidityProvider) return liquidityProvision;
if (!liquidityProvider) {
return {
...liquidityProvision,
partyId: liquidityProvision.party.id,
};
}
const accounts = compact(
liquidityProvision.party.accountsConnection?.edges
).map((e) => e.node);