Compare commits

...
2 changed files with 191 additions and 107 deletions
+139 -87
View File
@@ -35,6 +35,7 @@ const defaultFill: PartialDeep<Trade> = {
}, },
createdAt: new Date('2022-02-02T14:00:00').toISOString(), createdAt: new Date('2022-02-02T14:00:00').toISOString(),
}; };
describe('FillsTable', () => { describe('FillsTable', () => {
it('correct columns are rendered', async () => { it('correct columns are rendered', async () => {
// 7005-FILL-001 // 7005-FILL-001
@@ -271,96 +272,147 @@ describe('FillsTable', () => {
.find((c) => c.getAttribute('col-id') === 'size'); .find((c) => c.getAttribute('col-id') === 'size');
expect(sizeCell).toHaveTextContent('3,000,000,000'); expect(sizeCell).toHaveTextContent('3,000,000,000');
}); });
});
describe('FeesDiscountBreakdownTooltip', () => { describe('FeesDiscountBreakdownTooltip', () => {
it('shows all discounts', () => { it('shows all discounts', () => {
const data = generateFill({ const data = generateFill({
...defaultFill, ...defaultFill,
buyer: { buyer: {
id: partyId, id: partyId,
}, },
});
const props = {
data,
partyId,
value: data.market,
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0'];
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
const dt = container.querySelectorAll('dt');
const dd = container.querySelectorAll('dd');
const expectedDt = [
'Infrastructure Fee',
'Referral Discount',
'Volume Discount',
'Liquidity Fee',
'Referral Discount',
'Volume Discount',
'Maker Fee',
'Referral Discount',
'Volume Discount',
];
const expectedDD = [
'0.05 BTC',
'0.06 BTC',
'0.01 BTC',
'0.02 BTC',
'0.03 BTC',
'0.04 BTC',
];
expectedDt.forEach((label, i) => {
expect(dt[i]).toHaveTextContent(label);
});
expectedDD.forEach((label, i) => {
expect(dd[i]).toHaveTextContent(label);
});
}); });
const props = { });
data,
partyId, describe('getFeesBreakdown', () => {
value: data.market, it('should return correct fees breakdown for a taker', () => {
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0']; const fees = {
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />); makerFee: '1000',
const dt = container.querySelectorAll('dt'); infrastructureFee: '2000',
const dd = container.querySelectorAll('dd'); liquidityFee: '3000',
const expectedDt = [ };
'Infrastructure Fee', const expectedBreakdown = {
'Referral Discount', infrastructureFee: '2000',
'Volume Discount', liquidityFee: '3000',
'Liquidity Fee', makerFee: '1000',
'Referral Discount', totalFee: '6000',
'Volume Discount', };
'Maker Fee', expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
'Referral Discount',
'Volume Discount',
];
const expectedDD = [
'0.05 BTC',
'0.06 BTC',
'0.01 BTC',
'0.02 BTC',
'0.03 BTC',
'0.04 BTC',
];
expectedDt.forEach((label, i) => {
expect(dt[i]).toHaveTextContent(label);
}); });
expectedDD.forEach((label, i) => {
expect(dd[i]).toHaveTextContent(label); it('should return correct fees breakdown for a maker if market', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '-1000',
totalFee: '-1000',
};
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
});
it('should return correct fees breakdown for a maker if market is active', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '-1000',
totalFee: '-1000',
};
expect(
getFeesBreakdown('Maker', fees, Schema.MarketState.STATE_ACTIVE)
).toEqual(expectedBreakdown);
});
it('should return correct fees breakdown for a maker if the market is suspended', () => {
const fees = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '0',
};
const expectedBreakdown = {
infrastructureFee: '1000',
liquidityFee: '1500',
makerFee: '0',
totalFee: '2500',
};
expect(
getFeesBreakdown('Maker', fees, Schema.MarketState.STATE_SUSPENDED)
).toEqual(expectedBreakdown);
});
it('should return correct fees breakdown for a taker if the market is suspended', () => {
const fees = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '0',
};
const expectedBreakdown = {
infrastructureFee: '1000',
liquidityFee: '1500',
makerFee: '0',
totalFee: '2500',
};
expect(
getFeesBreakdown('Taker', fees, Schema.MarketState.STATE_SUSPENDED)
).toEqual(expectedBreakdown);
});
});
describe('getTotalFeesDiscounts', () => {
it('should return correct total value', () => {
const fees = {
infrastructureFeeReferralDiscount: '1',
infrastructureFeeVolumeDiscount: '2',
liquidityFeeReferralDiscount: '3',
liquidityFeeVolumeDiscount: '4',
makerFeeReferralDiscount: '5',
makerFeeVolumeDiscount: '6',
};
expect(getTotalFeesDiscounts(fees as TradeFeeFieldsFragment)).toEqual(
(1 + 2 + 3 + 4 + 5 + 6).toString()
);
}); });
}); });
}); });
describe('getFeesBreakdown', () => {
it('should return correct fees breakdown for a taker', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '1000',
totalFee: '6000',
};
expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
});
it('should return correct fees breakdown for a maker', () => {
const fees = {
makerFee: '1000',
infrastructureFee: '2000',
liquidityFee: '3000',
};
const expectedBreakdown = {
infrastructureFee: '2000',
liquidityFee: '3000',
makerFee: '-1000',
totalFee: '4000',
};
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
});
});
describe('getTotalFeesDiscounts', () => {
it('should return correct total value', () => {
const fees = {
infrastructureFeeReferralDiscount: '1',
infrastructureFeeVolumeDiscount: '2',
liquidityFeeReferralDiscount: '3',
liquidityFeeVolumeDiscount: '4',
makerFeeReferralDiscount: '5',
makerFeeVolumeDiscount: '6',
};
expect(getTotalFeesDiscounts(fees as TradeFeeFieldsFragment)).toEqual(
(1 + 2 + 3 + 4 + 5 + 6).toString()
);
});
});
+52 -20
View File
@@ -1,10 +1,11 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { import {
AgGridReact, type AgGridReact,
AgGridReactProps, type AgGridReactProps,
AgReactUiProps, type AgReactUiProps,
} from 'ag-grid-react'; } from 'ag-grid-react';
import type { ITooltipParams, ColDef } from 'ag-grid-community'; import type { ColDef } from 'ag-grid-community';
import type { ITooltipParams } from 'ag-grid-community';
import { import {
addDecimal, addDecimal,
addDecimalsFormatNumber, addDecimalsFormatNumber,
@@ -290,6 +291,7 @@ export const getRoleAndFees = ({
}) => { }) => {
let role: Role; let role: Role;
let fees; let fees;
if (data?.buyer.id === partyId) { if (data?.buyer.id === partyId) {
if (data.aggressor === Schema.Side.SIDE_BUY) { if (data.aggressor === Schema.Side.SIDE_BUY) {
role = TAKER; role = TAKER;
@@ -315,7 +317,16 @@ export const getRoleAndFees = ({
} else { } else {
return { role: '-', fees: undefined }; return { role: '-', fees: undefined };
} }
return { role, fees };
// We make the assumption that the market state is active if the maker fee is zero on both sides
// This needs to be updated when we have a way to get the correct market state when that fill happened from the API
// because the maker fee factor can be set to 0 via governance
const marketState =
data?.buyerFee.makerFee === data.sellerFee.makerFee &&
new BigNumber(data?.buyerFee.makerFee).isZero()
? Schema.MarketState.STATE_SUSPENDED
: Schema.MarketState.STATE_ACTIVE;
return { role, fees, marketState };
}; };
const FeesBreakdownTooltip = ({ const FeesBreakdownTooltip = ({
@@ -329,16 +340,21 @@ const FeesBreakdownTooltip = ({
const asset = getAsset(market); const asset = getAsset(market);
const { role, fees } = getRoleAndFees({ data, partyId }) ?? {}; const { role, fees, marketState } = getRoleAndFees({ data, partyId }) ?? {};
if (!fees) return null; if (!fees) return null;
const { infrastructureFee, liquidityFee, makerFee, totalFee } = const { infrastructureFee, liquidityFee, makerFee, totalFee } =
getFeesBreakdown(role, fees); getFeesBreakdown(role, fees, marketState);
return ( return (
<div <div
data-testid="fee-breakdown-tooltip" data-testid="fee-breakdown-tooltip"
className="z-20 max-w-sm px-4 py-2 text-sm text-black border rounded bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word dark:text-white" className="z-20 max-w-sm px-4 py-2 text-xs text-black border rounded bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word dark:text-white"
> >
<p className="mb-1 italic">
{t('If the market was %s', [
Schema.MarketStateMapping[marketState].toLowerCase(),
])}
</p>
{role === MAKER && ( {role === MAKER && (
<> <>
<p className="mb-1">{t('The maker will receive the maker fee.')}</p> <p className="mb-1">{t('The maker will receive the maker fee.')}</p>
@@ -352,7 +368,7 @@ const FeesBreakdownTooltip = ({
{role === TAKER && ( {role === TAKER && (
<p className="mb-1">{t('Fees to be paid by the taker.')}</p> <p className="mb-1">{t('Fees to be paid by the taker.')}</p>
)} )}
{role === '-' && ( {(role === '-' || marketState === Schema.MarketState.STATE_SUSPENDED) && (
<p className="mb-1"> <p className="mb-1">
{t( {t(
'If the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.' 'If the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.'
@@ -393,8 +409,8 @@ const FeesDiscountBreakdownTooltipItem = ({
}) => }) =>
value && value !== '0' ? ( value && value !== '0' ? (
<> <>
<dt className="col-span-1">{label}</dt> <dt className="col-span-2">{label}</dt>
<dd className="text-right col-span-1"> <dd className="text-right col-span-2">
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol} {addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
</dd> </dd>
</> </>
@@ -417,7 +433,7 @@ export const FeesDiscountBreakdownTooltip = ({
data-testid="fee-discount-breakdown-tooltip" data-testid="fee-discount-breakdown-tooltip"
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white" className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
> >
<dl className="grid grid-cols-2 gap-x-1"> <dl className="grid grid-cols-6 gap-x-1 text-xs">
{(fees.infrastructureFeeReferralDiscount || '0') !== '0' || {(fees.infrastructureFeeReferralDiscount || '0') !== '0' ||
(fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? ( (fees.infrastructureFeeVolumeDiscount || '0') !== '0' ? (
<dt className="col-span-2">{t('Infrastructure Fee')}</dt> <dt className="col-span-2">{t('Infrastructure Fee')}</dt>
@@ -479,20 +495,36 @@ export const getTotalFeesDiscounts = (fees: TradeFeeFieldsFragment) => {
export const getFeesBreakdown = ( export const getFeesBreakdown = (
role: Role, role: Role,
feesObj: TradeFeeFieldsFragment feesObj: TradeFeeFieldsFragment,
marketState: Schema.MarketState = Schema.MarketState.STATE_ACTIVE
) => { ) => {
const makerFee = // If market is in auction we assume maker fee is zero
role === MAKER const isMarketActive = marketState === Schema.MarketState.STATE_ACTIVE;
? new BigNumber(feesObj.makerFee).times(-1).toString()
: feesObj.makerFee;
const infrastructureFee = feesObj.infrastructureFee; // If role is taker, then these are the fees to be paid
const liquidityFee = feesObj.liquidityFee; let { makerFee, infrastructureFee, liquidityFee } = feesObj;
if (isMarketActive) {
if (role === MAKER) {
makerFee = new BigNumber(feesObj.makerFee).times(-1).toString();
infrastructureFee = '0';
liquidityFee = '0';
}
} else {
// If market is suspended (in monitoring auction), then half of the fees are paid
infrastructureFee = new BigNumber(infrastructureFee)
.dividedBy(2)
.toString();
liquidityFee = new BigNumber(liquidityFee).dividedBy(2).toString();
// maker fee is already zero
makerFee = '0';
}
const totalFee = new BigNumber(infrastructureFee) const totalFee = new BigNumber(infrastructureFee)
.plus(makerFee) .plus(makerFee)
.plus(liquidityFee) .plus(liquidityFee)
.toString(); .toString();
return { return {
infrastructureFee, infrastructureFee,
liquidityFee, liquidityFee,