Merge pull request #4905 from vegaprotocol/chore/update-develop-with-mainnet-fixes

chore(trading,deal-ticket,market-depth,ci): update develop with mainnet fixes
This commit is contained in:
Matthew Russell 2023-09-26 16:30:10 -04:00 committed by GitHub
commit 77c0894501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 161 additions and 46 deletions

View File

@ -53,7 +53,7 @@ export const HeaderStat = ({
<div data-testid="item-header" id={id}> <div data-testid="item-header" id={id}>
{heading} {heading}
</div> </div>
<Tooltip description={description}> <Tooltip description={description} underline>
<div <div
data-testid="item-value" data-testid="item-value"
aria-labelledby={id} aria-labelledby={id}

View File

@ -110,8 +110,8 @@ html [data-theme='light'] {
--pennant-color-depth-sell-fill: theme(colors.market.red.DEFAULT); --pennant-color-depth-sell-fill: theme(colors.market.red.DEFAULT);
--pennant-color-depth-sell-stroke: theme(colors.market.red.650); --pennant-color-depth-sell-stroke: theme(colors.market.red.650);
--pennant-color-volume-buy: theme(colors.market.green.300); --pennant-color-volume-buy: theme(colors.market.green.DEFAULT);
--pennant-color-volume-sell: theme(colors.market.red.300); --pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
} }
html [data-theme='dark'] { html [data-theme='dark'] {
@ -132,7 +132,7 @@ html [data-theme='dark'] {
--pennant-color-depth-sell-stroke: theme(colors.market.red.DEFAULT); --pennant-color-depth-sell-stroke: theme(colors.market.red.DEFAULT);
--pennant-color-volume-buy: theme(colors.market.green.600); --pennant-color-volume-buy: theme(colors.market.green.600);
--pennant-color-volume-sell: theme(colors.market.red.650); --pennant-color-volume-sell: theme(colors.market.red.DEFAULT);
} }
/** /**

View File

@ -12,6 +12,7 @@ import { formatRange, formatValue } from '@vegaprotocol/utils';
import { marketMarginDataProvider } from '@vegaprotocol/accounts'; import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion'; import * as AccordionPrimitive from '@radix-ui/react-accordion';
import * as Schema from '@vegaprotocol/types';
import { import {
MARGIN_DIFF_TOOLTIP_TEXT, MARGIN_DIFF_TOOLTIP_TEXT,
@ -37,14 +38,16 @@ export interface DealTicketFeeDetailsProps {
assetSymbol: string; assetSymbol: string;
order: OrderSubmissionBody['orderSubmission']; order: OrderSubmissionBody['orderSubmission'];
market: Market; market: Market;
isMarketInAuction?: boolean;
} }
export const DealTicketFeeDetails = ({ export const DealTicketFeeDetails = ({
assetSymbol, assetSymbol,
order, order,
market, market,
isMarketInAuction,
}: DealTicketFeeDetailsProps) => { }: DealTicketFeeDetailsProps) => {
const feeEstimate = useEstimateFees(order); const feeEstimate = useEstimateFees(order, isMarketInAuction);
const asset = getAsset(market); const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset; const { decimals: assetDecimals, quantum } = asset;
@ -63,7 +66,7 @@ export const DealTicketFeeDetails = ({
<> <>
<span> <span>
{t( {t(
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.` `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.`
)} )}
</span> </span>
<FeesBreakdown <FeesBreakdown
@ -86,6 +89,7 @@ export interface DealTicketMarginDetailsProps {
onMarketClick?: (marketId: string, metaKey?: boolean) => void; onMarketClick?: (marketId: string, metaKey?: boolean) => void;
assetSymbol: string; assetSymbol: string;
positionEstimate: EstimatePositionQuery['estimatePosition']; positionEstimate: EstimatePositionQuery['estimatePosition'];
side: Schema.Side;
} }
export const DealTicketMarginDetails = ({ export const DealTicketMarginDetails = ({
@ -95,6 +99,7 @@ export const DealTicketMarginDetails = ({
market, market,
onMarketClick, onMarketClick,
positionEstimate, positionEstimate,
side,
}: DealTicketMarginDetailsProps) => { }: DealTicketMarginDetailsProps) => {
const [breakdownDialog, setBreakdownDialog] = useState(false); const [breakdownDialog, setBreakdownDialog] = useState(false);
const { pubKey: partyId } = useVegaWallet(); const { pubKey: partyId } = useVegaWallet();
@ -163,10 +168,7 @@ export const DealTicketMarginDetails = ({
: '0', : '0',
assetDecimals assetDecimals
)} )}
formattedValue={formatRange( formattedValue={formatValue(
deductionFromCollateralBestCase > 0
? deductionFromCollateralBestCase.toString()
: '0',
deductionFromCollateralWorstCase > 0 deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString() ? deductionFromCollateralWorstCase.toString()
: '0', : '0',
@ -185,8 +187,7 @@ export const DealTicketMarginDetails = ({
marginEstimate?.worstCase.initialLevel, marginEstimate?.worstCase.initialLevel,
assetDecimals assetDecimals
)} )}
formattedValue={formatRange( formattedValue={formatValue(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel, marginEstimate?.worstCase.initialLevel,
assetDecimals, assetDecimals,
quantum quantum
@ -198,6 +199,7 @@ export const DealTicketMarginDetails = ({
} }
let liquidationPriceEstimate = emptyValue; let liquidationPriceEstimate = emptyValue;
let liquidationPriceEstimateRange = emptyValue;
if (liquidationEstimate) { if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt( const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
@ -207,8 +209,7 @@ export const DealTicketMarginDetails = ({
liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '') liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '')
); );
const liquidationEstimateBestCase = const liquidationEstimateBestCase =
liquidationEstimateBestCaseIncludingBuyOrders > side === Schema.Side.SIDE_BUY
liquidationEstimateBestCaseIncludingSellOrders
? liquidationEstimateBestCaseIncludingBuyOrders ? liquidationEstimateBestCaseIncludingBuyOrders
: liquidationEstimateBestCaseIncludingSellOrders; : liquidationEstimateBestCaseIncludingSellOrders;
@ -219,14 +220,19 @@ export const DealTicketMarginDetails = ({
liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '') liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '')
); );
const liquidationEstimateWorstCase = const liquidationEstimateWorstCase =
liquidationEstimateWorstCaseIncludingBuyOrders > side === Schema.Side.SIDE_BUY
liquidationEstimateWorstCaseIncludingSellOrders
? liquidationEstimateWorstCaseIncludingBuyOrders ? liquidationEstimateWorstCaseIncludingBuyOrders
: liquidationEstimateWorstCaseIncludingSellOrders; : liquidationEstimateWorstCaseIncludingSellOrders;
// The estimate order query API gives us the liquidation price in formatted by asset decimals. // The estimate order query API gives us the liquidation price in formatted by asset decimals.
// We need to calculate it with asset decimals, but display it with market decimals precision until the API changes. // We need to calculate it with asset decimals, but display it with market decimals precision until the API changes.
liquidationPriceEstimate = formatRange( liquidationPriceEstimate = formatValue(
liquidationEstimateWorstCase.toString(),
assetDecimals,
undefined,
market.decimalPlaces
);
liquidationPriceEstimateRange = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase (liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase ? liquidationEstimateBestCase
: liquidationEstimateWorstCase : liquidationEstimateWorstCase
@ -249,7 +255,7 @@ export const DealTicketMarginDetails = ({
const quoteName = getQuoteName(market); const quoteName = getQuoteName(market);
return ( return (
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col w-full gap-2">
<Accordion> <Accordion>
<AccordionPanel <AccordionPanel
itemId="margin" itemId="margin"
@ -265,7 +271,7 @@ export const DealTicketMarginDetails = ({
<div <div
data-testid={`deal-ticket-fee-margin-required`} data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'} key={'value-dropdown'}
className="flex items-center gap-2 justify-between w-full" className="flex items-center justify-between w-full gap-2"
> >
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}> <Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
@ -282,11 +288,9 @@ export const DealTicketMarginDetails = ({
assetDecimals assetDecimals
) ?? '-' ) ?? '-'
} }
noUnderline
> >
<div className="font-mono text-right"> <div className="font-mono text-right">
{formatRange( {formatValue(
marginRequiredBestCase,
marginRequiredWorstCase, marginRequiredWorstCase,
assetDecimals, assetDecimals,
quantum quantum
@ -298,7 +302,7 @@ export const DealTicketMarginDetails = ({
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
} }
> >
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col w-full gap-2">
<KeyValue <KeyValue
label={t('Total margin available')} label={t('Total margin available')}
indent indent
@ -344,7 +348,7 @@ export const DealTicketMarginDetails = ({
{projectedMargin} {projectedMargin}
<KeyValue <KeyValue
label={t('Liquidation')} label={t('Liquidation')}
value={liquidationPriceEstimate} value={liquidationPriceEstimateRange}
formattedValue={liquidationPriceEstimate} formattedValue={liquidationPriceEstimate}
symbol={quoteName} symbol={quoteName}
labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT} labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}

View File

@ -36,7 +36,12 @@ import {
formatValue, formatValue,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { activeOrdersProvider } from '@vegaprotocol/orders'; import { activeOrdersProvider } from '@vegaprotocol/orders';
import { getAsset, getDerivedPrice, getQuoteName } from '@vegaprotocol/markets'; import {
getAsset,
getDerivedPrice,
getQuoteName,
isMarketInAuction,
} from '@vegaprotocol/markets';
import { import {
validateExpiration, validateExpiration,
validateMarketState, validateMarketState,
@ -182,6 +187,7 @@ export const DealTicket = ({
const iceberg = watch('iceberg'); const iceberg = watch('iceberg');
const peakSize = watch('peakSize'); const peakSize = watch('peakSize');
const expiresAt = watch('expiresAt'); const expiresAt = watch('expiresAt');
const postOnly = watch('postOnly');
useEffect(() => { useEffect(() => {
const size = storedFormValues?.[dealTicketType]?.size; const size = storedFormValues?.[dealTicketType]?.size;
@ -198,7 +204,7 @@ export const DealTicket = ({
}, [storedFormValues, dealTicketType, rawPrice, setValue]); }, [storedFormValues, dealTicketType, rawPrice, setValue]);
useEffect(() => { useEffect(() => {
const subscription = watch((value, { name, type }) => { const subscription = watch((value) => {
updateStoredFormValues(market.id, value); updateStoredFormValues(market.id, value);
}); });
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
@ -211,6 +217,7 @@ export const DealTicket = ({
size: rawSize, size: rawSize,
timeInForce, timeInForce,
type, type,
postOnly,
}, },
market.id, market.id,
market.decimalPlaces, market.decimalPlaces,
@ -219,8 +226,7 @@ export const DealTicket = ({
const price = const price =
normalizedOrder && normalizedOrder &&
marketPrice && getDerivedPrice(normalizedOrder, marketPrice ?? undefined);
getDerivedPrice(normalizedOrder, marketPrice);
const notionalSize = getNotionalSize( const notionalSize = getNotionalSize(
price, price,
@ -459,7 +465,7 @@ export const DealTicket = ({
)} )}
/> />
)} )}
<div className="mb-4 flex flex-col gap-2 w-full"> <div className="flex flex-col w-full mb-4 gap-2">
<KeyValue <KeyValue
label={t('Notional')} label={t('Notional')}
value={formatValue(notionalSize, market.decimalPlaces)} value={formatValue(notionalSize, market.decimalPlaces)}
@ -473,6 +479,7 @@ export const DealTicket = ({
} }
assetSymbol={assetSymbol} assetSymbol={assetSymbol}
market={market} market={market}
isMarketInAuction={isMarketInAuction(marketData.marketTradingMode)}
/> />
</div> </div>
<Controller <Controller
@ -675,6 +682,7 @@ export const DealTicket = ({
)} )}
</Button> </Button>
<DealTicketMarginDetails <DealTicketMarginDetails
side={normalizedOrder.side}
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
assetSymbol={asset.symbol} assetSymbol={asset.symbol}
marginAccountBalance={marginAccountBalance} marginAccountBalance={marginAccountBalance}

View File

@ -47,7 +47,7 @@ export const KeyValue = ({
<Tooltip description={labelDescription}> <Tooltip description={labelDescription}>
<div className="text-muted">{label}</div> <div className="text-muted">{label}</div>
</Tooltip> </Tooltip>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`} noUnderline> <Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
{valueElement} {valueElement}
</Tooltip> </Tooltip>
</div> </div>

View File

@ -0,0 +1,78 @@
import { renderHook } from '@testing-library/react';
import { useEstimateFees } from './use-estimate-fees';
import { Side, OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
const data: EstimateFeesQuery = {
estimateFees: {
totalFeeAmount: '12',
fees: {
infrastructureFee: '2',
liquidityFee: '4',
makerFee: '6',
},
},
};
const mockUseEstimateFeesQuery = jest.fn((...args) => ({
data,
}));
jest.mock('./__generated__/EstimateOrder', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useEstimateFeesQuery: jest.fn((...args) => mockUseEstimateFeesQuery(...args)),
}));
jest.mock('@vegaprotocol/wallet', () => ({
useVegaWallet: () => ({ pubKey: 'pubKey' }),
}));
describe('useEstimateFees', () => {
it('returns 0 as estimated values if order is postOnly', () => {
const { result } = renderHook(() =>
useEstimateFees({
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
postOnly: true,
})
);
expect(result.current).toEqual({
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '0',
},
});
expect(mockUseEstimateFeesQuery.mock.lastCall?.[0].skip).toBeTruthy();
});
it('divide values by 2 if market is in auction', () => {
const { result } = renderHook(() =>
useEstimateFees(
{
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
},
true
)
);
expect(result.current).toEqual({
totalFeeAmount: '6',
fees: {
infrastructureFee: '1',
liquidityFee: '2',
makerFee: '3',
},
});
});
});

View File

@ -1,13 +1,16 @@
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
export const useEstimateFees = ( const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey } = useVegaWallet();
export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission'],
isMarketInAuction?: boolean
): EstimateFeesQuery['estimateFees'] | undefined => {
const { pubKey } = useVegaWallet();
const { data } = useEstimateFeesQuery({ const { data } = useEstimateFeesQuery({
variables: order && { variables: order && {
marketId: order.marketId, marketId: order.marketId,
@ -19,7 +22,28 @@ export const useEstimateFees = (
type: order.type, type: order.type,
}, },
fetchPolicy: 'no-cache', fetchPolicy: 'no-cache',
skip: !pubKey || !order?.size || !order?.price, skip: !pubKey || !order?.size || !order?.price || order.postOnly,
}); });
return data?.estimateFees; if (order?.postOnly) {
return {
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '0',
},
};
}
return isMarketInAuction && data?.estimateFees
? {
totalFeeAmount: divideByTwo(data.estimateFees.totalFeeAmount),
fees: {
infrastructureFee: divideByTwo(
data.estimateFees.fees.infrastructureFee
),
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
makerFee: divideByTwo(data.estimateFees.fees.makerFee),
},
}
: data?.estimateFees;
}; };

View File

@ -175,7 +175,7 @@ export const Orderbook = ({
); );
return ( return (
<div className="h-full text-xs grid grid-rows-[1fr_min-content]"> <div className="h-full text-xs grid grid-rows-[1fr_min-content] overflow-hidden">
<div> <div>
<ReactVirtualizedAutoSizer> <ReactVirtualizedAutoSizer>
{({ width, height }) => { {({ width, height }) => {

View File

@ -60,6 +60,7 @@ export const FeesBreakdown = ({
.plus(fees.infrastructureFee) .plus(fees.infrastructureFee)
.plus(fees.liquidityFee) .plus(fees.liquidityFee)
.toString(); .toString();
if (totalFees === '0') return null;
const formatValue = (value: string | number | null | undefined): string => { const formatValue = (value: string | number | null | undefined): string => {
return value && !isNaN(Number(value)) return value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, decimals) ? addDecimalsFormatNumber(value, decimals)

View File

@ -33,7 +33,7 @@ export const getDerivedPrice = (
type: Schema.OrderType; type: Schema.OrderType;
price?: string | undefined; price?: string | undefined;
}, },
marketPrice: string marketPrice?: string
) => { ) => {
// If order type is market we should use either the mark price // If order type is market we should use either the mark price
// or the uncrossing price. If order type is limit use the price // or the uncrossing price. If order type is limit use the price

View File

@ -19,14 +19,14 @@ export interface TooltipProps {
align?: 'start' | 'center' | 'end'; align?: 'start' | 'center' | 'end';
side?: 'top' | 'right' | 'bottom' | 'left'; side?: 'top' | 'right' | 'bottom' | 'left';
sideOffset?: number; sideOffset?: number;
noUnderline?: boolean; underline?: boolean;
} }
export const TOOLTIP_TRIGGER_CLASS_NAME = (noUnderline?: boolean) => export const TOOLTIP_TRIGGER_CLASS_NAME = (underline?: boolean) =>
classNames( classNames({
{ 'underline underline-offset-2': !noUnderline }, 'underline underline-offset-2 decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed':
'decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed' underline,
); });
// Conditionally rendered tooltip if description content is provided. // Conditionally rendered tooltip if description content is provided.
export const Tooltip = ({ export const Tooltip = ({
@ -36,12 +36,12 @@ export const Tooltip = ({
sideOffset, sideOffset,
align = 'start', align = 'start',
side = 'bottom', side = 'bottom',
noUnderline, underline,
}: TooltipProps) => }: TooltipProps) =>
description ? ( description ? (
<Provider delayDuration={200} skipDelayDuration={100}> <Provider delayDuration={200} skipDelayDuration={100}>
<Root open={open}> <Root open={open}>
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(noUnderline)}> <Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(underline)}>
{children} {children}
</Trigger> </Trigger>
{description && ( {description && (