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:
macqbat 2022-11-03 16:17:44 +01:00 committed by GitHub
parent e36d571cbf
commit 596c273657
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 69 deletions

View File

@ -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';

View File

@ -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;
}; };

View File

@ -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',
});
}
);
}); });

View File

@ -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,

View File

@ -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('.');