Bug/1686 decimal issues with size and price (#1938)
* chore: fix order validation when market is in pending state * chore: fix order validation when market is in pending state * chore: fix order validation when market is in pending state * chore: fix order validation when market is in pending state - add some urgent memo * chore: fix order validation when market is in pending state - add unit test * chore: fix order validation when market is in pending state - add memos in right places Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
parent
e36d571cbf
commit
596c273657
@ -28,3 +28,5 @@ export const DEAL_TICKET_SECTION = {
|
|||||||
EXPIRY: 'sec-expiry',
|
EXPIRY: 'sec-expiry',
|
||||||
SUMMARY: 'sec-summary',
|
SUMMARY: 'sec-summary',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ERROR_SIZE_DECIMAL = 'step';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { AccountType } from '@vegaprotocol/types';
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
import { toBigNum } from '@vegaprotocol/react-helpers';
|
import { toBigNum } from '@vegaprotocol/react-helpers';
|
||||||
@ -33,15 +34,20 @@ export const useOrderMarginValidation = ({ market, estMargin }: Props) => {
|
|||||||
const margin = toBigNum(estMargin?.margin || 0, 0);
|
const margin = toBigNum(estMargin?.margin || 0, 0);
|
||||||
const { id, symbol, decimals } =
|
const { id, symbol, decimals } =
|
||||||
market.tradableInstrument.instrument.product.settlementAsset;
|
market.tradableInstrument.instrument.product.settlementAsset;
|
||||||
if (balance.isZero() || balance.isLessThan(margin)) {
|
const balanceString = balance.toString();
|
||||||
|
const marginString = margin.toString();
|
||||||
|
const memoizedValue = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
balance: balance.toString(),
|
balance: balanceString,
|
||||||
margin: margin.toString(),
|
margin: marginString,
|
||||||
id,
|
id,
|
||||||
symbol,
|
symbol,
|
||||||
decimals,
|
decimals,
|
||||||
};
|
};
|
||||||
}
|
}, [balanceString, marginString, id, symbol, decimals]);
|
||||||
|
|
||||||
|
if (balance.isZero() || balance.isLessThan(margin)) {
|
||||||
|
return memoizedValue;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -11,10 +11,10 @@ import {
|
|||||||
} from '@vegaprotocol/types';
|
} from '@vegaprotocol/types';
|
||||||
import type { ValidationProps } from './use-order-validation';
|
import type { ValidationProps } from './use-order-validation';
|
||||||
import { marketTranslations, useOrderValidation } from './use-order-validation';
|
import { marketTranslations, useOrderValidation } from './use-order-validation';
|
||||||
import { ERROR_SIZE_DECIMAL } from './validate-size';
|
|
||||||
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
|
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
|
||||||
import * as OrderMarginValidation from './use-order-margin-validation';
|
import * as OrderMarginValidation from './use-order-margin-validation';
|
||||||
import { ValidateMargin } from './validate-margin';
|
import { ValidateMargin } from './validate-margin';
|
||||||
|
import { ERROR_SIZE_DECIMAL } from '../constants';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/wallet');
|
jest.mock('@vegaprotocol/wallet');
|
||||||
|
|
||||||
@ -174,12 +174,17 @@ describe('useOrderValidation', () => {
|
|||||||
`(
|
`(
|
||||||
'Returns an error message for market state suspended or pending',
|
'Returns an error message for market state suspended or pending',
|
||||||
({ state }) => {
|
({ state }) => {
|
||||||
|
jest
|
||||||
|
.spyOn(OrderMarginValidation, 'useOrderMarginValidation')
|
||||||
|
.mockReturnValue(false);
|
||||||
const { result } = setup({
|
const { result } = setup({
|
||||||
market: {
|
market: {
|
||||||
...defaultOrder.market,
|
...defaultOrder.market,
|
||||||
state,
|
state,
|
||||||
tradingMode: MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
|
tradingMode: MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
|
||||||
},
|
},
|
||||||
|
orderType: Schema.OrderType.TYPE_LIMIT,
|
||||||
|
orderTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||||
});
|
});
|
||||||
expect(result.current).toStrictEqual({
|
expect(result.current).toStrictEqual({
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
@ -302,4 +307,28 @@ describe('useOrderValidation', () => {
|
|||||||
testElement.type
|
testElement.type
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each`
|
||||||
|
state
|
||||||
|
${MarketState.STATE_PENDING}
|
||||||
|
${MarketState.STATE_PROPOSED}
|
||||||
|
`(
|
||||||
|
'Returns error when market state is pending and size is wrong',
|
||||||
|
({ state }) => {
|
||||||
|
const { result } = setup({
|
||||||
|
fieldErrors: {
|
||||||
|
size: { type: `validate`, message: ERROR_SIZE_DECIMAL },
|
||||||
|
},
|
||||||
|
market: {
|
||||||
|
...market,
|
||||||
|
state,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.current).toStrictEqual({
|
||||||
|
isDisabled: true,
|
||||||
|
message: ERROR.FIELD_PRICE_STEP_DECIMAL,
|
||||||
|
section: 'sec-size',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
@ -12,14 +12,13 @@ import {
|
|||||||
} from '@vegaprotocol/types';
|
} from '@vegaprotocol/types';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ERROR_SIZE_DECIMAL } from './validate-size';
|
|
||||||
import { MarketDataGrid } from '../trading-mode-tooltip';
|
import { MarketDataGrid } from '../trading-mode-tooltip';
|
||||||
import { compileGridData } from '../trading-mode-tooltip/compile-grid-data';
|
import { compileGridData } from '../trading-mode-tooltip/compile-grid-data';
|
||||||
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
|
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
|
||||||
import { ValidateMargin } from './validate-margin';
|
import { ValidateMargin } from './validate-margin';
|
||||||
import type { OrderMargin } from '../../hooks/use-order-margin';
|
import type { OrderMargin } from '../../hooks/use-order-margin';
|
||||||
import { useOrderMarginValidation } from './use-order-margin-validation';
|
import { useOrderMarginValidation } from './use-order-margin-validation';
|
||||||
import { DEAL_TICKET_SECTION } from '../constants';
|
import { DEAL_TICKET_SECTION, ERROR_SIZE_DECIMAL } from '../constants';
|
||||||
|
|
||||||
export const isMarketInAuction = (market: DealTicketMarketFragment) => {
|
export const isMarketInAuction = (market: DealTicketMarketFragment) => {
|
||||||
return [
|
return [
|
||||||
@ -53,7 +52,7 @@ export type DealTicketSection =
|
|||||||
|
|
||||||
export const useOrderValidation = ({
|
export const useOrderValidation = ({
|
||||||
market,
|
market,
|
||||||
fieldErrors = {},
|
fieldErrors,
|
||||||
orderType,
|
orderType,
|
||||||
orderTimeInForce,
|
orderTimeInForce,
|
||||||
estMargin,
|
estMargin,
|
||||||
@ -66,6 +65,80 @@ export const useOrderValidation = ({
|
|||||||
const minSize = toDecimal(market.positionDecimalPlaces);
|
const minSize = toDecimal(market.positionDecimalPlaces);
|
||||||
const isInvalidOrderMargin = useOrderMarginValidation({ market, estMargin });
|
const isInvalidOrderMargin = useOrderMarginValidation({ market, estMargin });
|
||||||
|
|
||||||
|
const fieldErrorChecking = useMemo<{
|
||||||
|
message: ReactNode | string;
|
||||||
|
isDisabled: boolean;
|
||||||
|
section: DealTicketSection;
|
||||||
|
} | null>(() => {
|
||||||
|
if (fieldErrors?.size?.type || fieldErrors?.price?.type) {
|
||||||
|
if (fieldErrors?.size?.type === 'required') {
|
||||||
|
return {
|
||||||
|
isDisabled: true,
|
||||||
|
message: t('You need to provide a size'),
|
||||||
|
section: DEAL_TICKET_SECTION.SIZE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldErrors?.size?.type === 'min') {
|
||||||
|
return {
|
||||||
|
isDisabled: true,
|
||||||
|
message: t(`Size cannot be lower than "${minSize}"`),
|
||||||
|
section: DEAL_TICKET_SECTION.SIZE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
fieldErrors?.price?.type === 'required' &&
|
||||||
|
orderType !== Schema.OrderType.TYPE_MARKET
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
isDisabled: true,
|
||||||
|
message: t('You need to provide a price'),
|
||||||
|
section: DEAL_TICKET_SECTION.PRICE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
fieldErrors?.price?.type === 'min' &&
|
||||||
|
orderType !== Schema.OrderType.TYPE_MARKET
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
isDisabled: true,
|
||||||
|
message: t(`The price cannot be negative`),
|
||||||
|
section: DEAL_TICKET_SECTION.PRICE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
fieldErrors?.size?.type === 'validate' &&
|
||||||
|
fieldErrors?.size?.message === ERROR_SIZE_DECIMAL
|
||||||
|
) {
|
||||||
|
if (market.positionDecimalPlaces === 0) {
|
||||||
|
return {
|
||||||
|
isDisabled: true,
|
||||||
|
message: t('Order sizes must be in whole numbers for this market'),
|
||||||
|
section: DEAL_TICKET_SECTION.SIZE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isDisabled: true,
|
||||||
|
message: t(
|
||||||
|
`The size field accepts up to ${market.positionDecimalPlaces} decimal places`
|
||||||
|
),
|
||||||
|
section: DEAL_TICKET_SECTION.SIZE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [
|
||||||
|
fieldErrors?.size?.type,
|
||||||
|
fieldErrors?.size?.message,
|
||||||
|
fieldErrors?.price?.type,
|
||||||
|
minSize,
|
||||||
|
orderType,
|
||||||
|
market.positionDecimalPlaces,
|
||||||
|
]);
|
||||||
|
|
||||||
const { message, isDisabled, section } = useMemo<{
|
const { message, isDisabled, section } = useMemo<{
|
||||||
message: ReactNode | string;
|
message: ReactNode | string;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
@ -104,6 +177,9 @@ export const useOrderValidation = ({
|
|||||||
market.state
|
market.state
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
if (fieldErrorChecking) {
|
||||||
|
return fieldErrorChecking;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
message: t(
|
message: t(
|
||||||
@ -243,62 +319,8 @@ export const useOrderValidation = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldErrors?.size?.type === 'required') {
|
if (fieldErrorChecking) {
|
||||||
return {
|
return fieldErrorChecking;
|
||||||
isDisabled: true,
|
|
||||||
message: t('You need to provide a size'),
|
|
||||||
section: DEAL_TICKET_SECTION.SIZE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldErrors?.size?.type === 'min') {
|
|
||||||
return {
|
|
||||||
isDisabled: true,
|
|
||||||
message: t(`Size cannot be lower than "${minSize}"`),
|
|
||||||
section: DEAL_TICKET_SECTION.SIZE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
fieldErrors?.price?.type === 'required' &&
|
|
||||||
orderType !== Schema.OrderType.TYPE_MARKET
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
isDisabled: true,
|
|
||||||
message: t('You need to provide a price'),
|
|
||||||
section: DEAL_TICKET_SECTION.PRICE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
fieldErrors?.price?.type === 'min' &&
|
|
||||||
orderType !== Schema.OrderType.TYPE_MARKET
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
isDisabled: true,
|
|
||||||
message: t(`The price cannot be negative`),
|
|
||||||
section: DEAL_TICKET_SECTION.PRICE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
fieldErrors?.size?.type === 'validate' &&
|
|
||||||
fieldErrors?.size?.message === ERROR_SIZE_DECIMAL
|
|
||||||
) {
|
|
||||||
if (market.positionDecimalPlaces === 0) {
|
|
||||||
return {
|
|
||||||
isDisabled: true,
|
|
||||||
message: t('Order sizes must be in whole numbers for this market'),
|
|
||||||
section: DEAL_TICKET_SECTION.SIZE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isDisabled: true,
|
|
||||||
message: t(
|
|
||||||
`The size field accepts up to ${market.positionDecimalPlaces} decimal places`
|
|
||||||
),
|
|
||||||
section: DEAL_TICKET_SECTION.SIZE,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInvalidOrderMargin) {
|
if (isInvalidOrderMargin) {
|
||||||
@ -331,12 +353,9 @@ export const useOrderValidation = ({
|
|||||||
section: '',
|
section: '',
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
minSize,
|
|
||||||
pubKey,
|
pubKey,
|
||||||
market,
|
market,
|
||||||
fieldErrors?.size?.type,
|
fieldErrorChecking,
|
||||||
fieldErrors?.size?.message,
|
|
||||||
fieldErrors?.price?.type,
|
|
||||||
orderType,
|
orderType,
|
||||||
orderTimeInForce,
|
orderTimeInForce,
|
||||||
isInvalidOrderMargin,
|
isInvalidOrderMargin,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const ERROR_SIZE_DECIMAL = 'step';
|
import { ERROR_SIZE_DECIMAL } from '../constants';
|
||||||
|
|
||||||
export const validateSize = (step: number) => {
|
export const validateSize = (step: number) => {
|
||||||
const [, stepDecimals = ''] = String(step).split('.');
|
const [, stepDecimals = ''] = String(step).split('.');
|
||||||
|
Loading…
Reference in New Issue
Block a user