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',
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 { AccountType } from '@vegaprotocol/types';
import { toBigNum } from '@vegaprotocol/react-helpers';
@ -33,15 +34,20 @@ export const useOrderMarginValidation = ({ market, estMargin }: Props) => {
const margin = toBigNum(estMargin?.margin || 0, 0);
const { id, symbol, decimals } =
market.tradableInstrument.instrument.product.settlementAsset;
if (balance.isZero() || balance.isLessThan(margin)) {
const balanceString = balance.toString();
const marginString = margin.toString();
const memoizedValue = useMemo(() => {
return {
balance: balance.toString(),
margin: margin.toString(),
balance: balanceString,
margin: marginString,
id,
symbol,
decimals,
};
}
}, [balanceString, marginString, id, symbol, decimals]);
if (balance.isZero() || balance.isLessThan(margin)) {
return memoizedValue;
}
return false;
};

View File

@ -11,10 +11,10 @@ import {
} from '@vegaprotocol/types';
import type { ValidationProps } 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 * as OrderMarginValidation from './use-order-margin-validation';
import { ValidateMargin } from './validate-margin';
import { ERROR_SIZE_DECIMAL } from '../constants';
jest.mock('@vegaprotocol/wallet');
@ -174,12 +174,17 @@ describe('useOrderValidation', () => {
`(
'Returns an error message for market state suspended or pending',
({ state }) => {
jest
.spyOn(OrderMarginValidation, 'useOrderMarginValidation')
.mockReturnValue(false);
const { result } = setup({
market: {
...defaultOrder.market,
state,
tradingMode: MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
},
orderType: Schema.OrderType.TYPE_LIMIT,
orderTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
});
expect(result.current).toStrictEqual({
isDisabled: false,
@ -302,4 +307,28 @@ describe('useOrderValidation', () => {
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';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import { ERROR_SIZE_DECIMAL } from './validate-size';
import { MarketDataGrid } from '../trading-mode-tooltip';
import { compileGridData } from '../trading-mode-tooltip/compile-grid-data';
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
import { ValidateMargin } from './validate-margin';
import type { OrderMargin } from '../../hooks/use-order-margin';
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) => {
return [
@ -53,7 +52,7 @@ export type DealTicketSection =
export const useOrderValidation = ({
market,
fieldErrors = {},
fieldErrors,
orderType,
orderTimeInForce,
estMargin,
@ -66,6 +65,80 @@ export const useOrderValidation = ({
const minSize = toDecimal(market.positionDecimalPlaces);
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<{
message: ReactNode | string;
isDisabled: boolean;
@ -104,6 +177,9 @@ export const useOrderValidation = ({
market.state
)
) {
if (fieldErrorChecking) {
return fieldErrorChecking;
}
return {
isDisabled: false,
message: t(
@ -243,62 +319,8 @@ export const useOrderValidation = ({
}
}
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,
};
if (fieldErrorChecking) {
return fieldErrorChecking;
}
if (isInvalidOrderMargin) {
@ -331,12 +353,9 @@ export const useOrderValidation = ({
section: '',
};
}, [
minSize,
pubKey,
market,
fieldErrors?.size?.type,
fieldErrors?.size?.message,
fieldErrors?.price?.type,
fieldErrorChecking,
orderType,
orderTimeInForce,
isInvalidOrderMargin,

View File

@ -1,4 +1,4 @@
export const ERROR_SIZE_DECIMAL = 'step';
import { ERROR_SIZE_DECIMAL } from '../constants';
export const validateSize = (step: number) => {
const [, stepDecimals = ''] = String(step).split('.');