Compare commits

...
Author SHA1 Message Date
Dariusz Majcherczyk 2fb068540e chore: update test 2024-03-08 17:39:42 +01:00
asiaznik 36bd0d84e6 fix: clear before typing 2024-03-08 14:57:51 +01:00
asiaznik b26c1bac7a chore: pass tpsl errors 2024-03-08 14:46:54 +01:00
asiaznik dbe31e6a67 chore: remove redundant error placeholders 2024-03-08 14:44:19 +01:00
asiaznik a12bf26cb4 chore: translate replacement keys 2024-03-08 14:38:41 +01:00
asiaznik c7e79aa196 fix: edit order dialog 2024-03-08 14:31:45 +01:00
asiaznik 8f334f42ad chore: mocks 2024-03-08 14:31:45 +01:00
asiaznik 968ec4f3f8 fix: unit tests 2024-03-08 14:31:45 +01:00
asiaznik 08b31ea43e chore: add tick size to info panel, move determine func to utils 2024-03-08 14:31:45 +01:00
asiaznik 513cebbcfc chore(trading): market's tick size 2024-03-08 14:30:29 +01:00
28 changed files with 208 additions and 97 deletions
@@ -9,12 +9,14 @@ from actions.utils import next_epoch, change_keys
from wallet_config import MM_WALLET
from vega_sim.null_service import VegaServiceNull
@pytest.fixture(scope="module")
def setup_environment(request, browser) -> Generator[Tuple[Page, str, str], None, None]:
with init_vega(request) as vega_instance:
request.addfinalizer(lambda: cleanup_container(vega_instance))
tDAI_market, tDAI_asset_id = setup_market_with_reward_program(vega_instance)
tDAI_market, tDAI_asset_id = setup_market_with_reward_program(
vega_instance)
with init_page(vega_instance, browser, request) as page:
risk_accepted_setup(page)
@@ -147,14 +149,12 @@ def setup_market_with_reward_program(vega: VegaServiceNull):
return tDAI_market, tDAI_asset_id
def test_network_reward_pot( setup_environment: Tuple[Page, str, str],
) -> None:
def test_network_reward_pot(setup_environment: Tuple[Page, str, str],
) -> None:
page, tDAI_market, tDAI_asset_id = setup_environment
expect(page.get_by_test_id(TOTAL_REWARDS)).to_have_text("183.33333 tDAI")
def test_reward_multiplier(
setup_environment: Tuple[Page, str, str],
) -> None:
@@ -168,7 +168,6 @@ def test_reward_multiplier(
)
def test_reward_history(
setup_environment: Tuple[Page, str, str],
) -> None:
@@ -177,26 +176,37 @@ def test_reward_history(
expect((page.get_by_role(ROW).locator(PRICE_TAKING_COL_ID)).nth(1)).to_have_text(
"299.99999100.00%"
)
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text("299.99999")
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(
1)).to_have_text("299.99999")
page.get_by_test_id(EARNED_BY_ME_BUTTON).click()
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text(
"183.33333"
)
def test_staking_reward(
page: Page,
):
setup_environment: Tuple[Page, str, str],
) -> None:
page, tDAI_market, tDAI_asset_id = setup_environment
expect(page.get_by_test_id("active-rewards-card")).to_have_count(2)
staking_reward_card = page.get_by_test_id("active-rewards-card").nth(1)
expect(staking_reward_card).to_be_visible()
expect(staking_reward_card.get_by_test_id("entity-scope")).to_have_text("Individual")
expect(staking_reward_card.get_by_test_id("locked-for")).to_have_text("0 epochs")
expect(staking_reward_card.get_by_test_id("reward-value")).to_have_text("100.00")
expect(staking_reward_card.get_by_test_id("distribution-strategy")).to_have_text("Pro rata")
expect(staking_reward_card.get_by_test_id(
"entity-scope")).to_have_text("Individual")
expect(staking_reward_card.get_by_test_id(
"locked-for")).to_have_text("0 epochs")
expect(staking_reward_card.get_by_test_id(
"reward-value")).to_have_text("100.00")
expect(staking_reward_card.get_by_test_id(
"distribution-strategy")).to_have_text("Pro rata")
expect(staking_reward_card.get_by_test_id("dispatch-metric-info")).to_have_text(
"Staking rewards"
)
expect(staking_reward_card.get_by_test_id("assessed-over")).to_have_text("1 epoch")
expect(staking_reward_card.get_by_test_id("scope")).to_have_text("Individual")
expect(staking_reward_card.get_by_test_id("staking-requirement")).to_have_text("1.00")
expect(staking_reward_card.get_by_test_id("average-position")).to_have_text("0.00")
expect(staking_reward_card.get_by_test_id(
"assessed-over")).to_have_text("1 epoch")
expect(staking_reward_card.get_by_test_id(
"scope")).to_have_text("Individual")
expect(staking_reward_card.get_by_test_id(
"staking-requirement")).to_have_text("1.00")
expect(staking_reward_card.get_by_test_id(
"average-position")).to_have_text("0.00")
@@ -1,10 +1,9 @@
import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
import { determinePriceStep, useValidateAmount } from '@vegaprotocol/utils';
import {
TradingFormGroup,
TradingInputError,
Tooltip,
FormGroup,
Input,
@@ -30,31 +29,7 @@ export const DealTicketPriceTakeProfitStopLoss = ({
}: DealTicketPriceTakeProfitStopLossProps) => {
const t = useT();
const validateAmount = useValidateAmount();
const priceStep = toDecimal(market?.decimalPlaces);
const renderTakeProfitError = () => {
if (takeProfitError) {
return (
<TradingInputError testId="deal-ticket-take-profit-error-message">
{takeProfitError}
</TradingInputError>
);
}
return null;
};
const renderStopLossError = () => {
if (stopLossError) {
return (
<TradingInputError testId="deal-stop-loss-error-message">
{stopLossError}
</TradingInputError>
);
}
return null;
};
const priceStep = determinePriceStep(market);
return (
<div className="mb-2">
@@ -104,9 +79,9 @@ export const DealTicketPriceTakeProfitStopLoss = ({
{...field}
/>
</FormGroup>
{fieldState.error && (
{(fieldState.error || takeProfitError) && (
<InputError testId="deal-ticket-error-message-price-take-profit">
{fieldState.error.message}
{fieldState.error?.message || takeProfitError}
</InputError>
)}
</div>
@@ -154,9 +129,9 @@ export const DealTicketPriceTakeProfitStopLoss = ({
{...field}
/>
</FormGroup>
{fieldState.error && (
{(fieldState.error || stopLossError) && (
<InputError testId="deal-ticket-error-message-price-stop-loss">
{fieldState.error.message}
{fieldState.error?.message || stopLossError}
</InputError>
)}
</div>
@@ -165,8 +140,6 @@ export const DealTicketPriceTakeProfitStopLoss = ({
</TradingFormGroup>
</div>
</div>
{renderTakeProfitError()}
{renderStopLossError()}
</div>
);
};
@@ -1,7 +1,7 @@
import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
import { useValidateAmount } from '@vegaprotocol/utils';
import {
TradingFormGroup,
TradingInput,
@@ -9,6 +9,7 @@ import {
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../use-t';
import { determineSizeStep } from '@vegaprotocol/utils';
export interface DealTicketSizeIcebergProps {
control: Control<OrderFormValues>;
@@ -29,7 +30,7 @@ export const DealTicketSizeIceberg = ({
}: DealTicketSizeIcebergProps) => {
const t = useT();
const validateAmount = useValidateAmount();
const sizeStep = toDecimal(market?.positionDecimalPlaces);
const sizeStep = determineSizeStep(market);
const renderPeakSizeError = () => {
if (peakSizeError) {
@@ -8,7 +8,6 @@ import {
formatForInput,
formatValue,
removeDecimal,
toDecimal,
useValidateAmount,
} from '@vegaprotocol/utils';
import { type Control, type UseFormWatch } from 'react-hook-form';
@@ -59,6 +58,7 @@ import { KeyValue } from './key-value';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { stopOrdersProvider } from '@vegaprotocol/orders';
import { useT } from '../../use-t';
import { determinePriceStep, determineSizeStep } from '@vegaprotocol/utils';
export interface StopOrderProps {
market: Market;
@@ -904,8 +904,8 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
market.tradableInstrument.instrument.metadata.tags
);
const sizeStep = toDecimal(market?.positionDecimalPlaces);
const priceStep = toDecimal(market?.decimalPlaces);
const sizeStep = determineSizeStep(market);
const priceStep = determinePriceStep(market);
useController({
name: 'type',
@@ -883,7 +883,7 @@ describe('DealTicket', () => {
'deal-ticket-error-message-price'
);
expect(errorMessage).toHaveTextContent(
'Price accepts up to 2 decimal places'
'Price must be a multiple of 0.01 for this market'
);
});
});
@@ -32,7 +32,6 @@ import {
toBigNum,
removeDecimal,
useValidateAmount,
toDecimal,
formatForInput,
formatValue,
} from '@vegaprotocol/utils';
@@ -82,6 +81,7 @@ import { DocsLinks } from '@vegaprotocol/environment';
import { useT } from '../../use-t';
import { DealTicketPriceTakeProfitStopLoss } from './deal-ticket-price-tp-sl';
import uniqueId from 'lodash/uniqueId';
import { determinePriceStep, determineSizeStep } from '@vegaprotocol/utils';
export const REDUCE_ONLY_TOOLTIP =
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
@@ -419,11 +419,12 @@ export const DealTicket = ({
},
});
const priceStep = toDecimal(market?.decimalPlaces);
const sizeStep = toDecimal(market?.positionDecimalPlaces);
const sizeStep = determineSizeStep(market);
const quoteName = getQuoteName(market);
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
const priceStep = determinePriceStep(market);
return (
<form
onSubmit={
+1
View File
@@ -11,6 +11,7 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
positionDecimalPlaces: 1,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
state: Schema.MarketState.STATE_ACTIVE,
tickSize: '1',
marketTimestamps: {
__typename: 'MarketTimestamps',
close: '',
+7 -1
View File
@@ -20,5 +20,11 @@
"jest.config.ts",
"__mocks__"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
"include": [
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx",
"../utils/src/lib/step.ts"
]
}
+1
View File
@@ -54,6 +54,7 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
decimalPlaces: 5,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
tickSize: '1',
fees: {
__typename: 'Fees',
factors: {
@@ -22,7 +22,7 @@ export const generateFundingPayment = (
decimalPlaces: 5,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
tickSize: '1',
fees: {
__typename: 'Fees',
factors: {
File diff suppressed because one or more lines are too long
@@ -139,6 +139,7 @@ query MarketInfo($marketId: ID!) {
id
decimalPlaces
positionDecimalPlaces
tickSize
state
tradingMode
linearSlippageFactor
File diff suppressed because one or more lines are too long
@@ -23,6 +23,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
determinePriceStep,
formatNumber,
formatNumberPercentage,
getDateTimeFormat,
@@ -320,6 +321,7 @@ export const KeyDetailsInfoPanel = ({
marketDecimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
settlementAssetDecimalPlaces: assetDecimals,
tickSize: determinePriceStep(market),
}
: {
name: market.tradableInstrument.instrument.name,
@@ -330,6 +332,7 @@ export const KeyDetailsInfoPanel = ({
marketDecimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
settlementAssetDecimalPlaces: assetDecimals,
tickSize: determinePriceStep(market),
}
}
parentData={
@@ -14,6 +14,7 @@ export const marketInfoQuery = (
positionDecimalPlaces: 0,
state: Schema.MarketState.STATE_ACTIVE,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
tickSize: '1',
proposal: {
__typename: 'Proposal',
id: 'market-0',
+1
View File
@@ -2,6 +2,7 @@ fragment MarketFields on Market {
id
decimalPlaces
positionDecimalPlaces
tickSize
state
tradingMode
parentMarketID
+1
View File
@@ -37,6 +37,7 @@ export const createMarketFragment = (
positionDecimalPlaces: 0,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
state: Schema.MarketState.STATE_ACTIVE,
tickSize: '1',
marketTimestamps: {
__typename: 'MarketTimestamps',
close: null,
@@ -11,6 +11,7 @@ export const generateOrder = (partialOrder?: PartialDeep<Order>) => {
__typename: 'Market',
id: 'market-id',
decimalPlaces: 1,
tickSize: '1',
fees: {
__typename: 'Fees',
factors: {
@@ -21,6 +21,7 @@ export const generateStopOrder = (
__typename: 'Market',
id: 'market-id',
decimalPlaces: 1,
tickSize: '1',
fees: {
__typename: 'Fees',
factors: {
@@ -17,6 +17,7 @@ describe('OrderEditDialog', () => {
);
const editOrder = await screen.findByTestId('edit-order');
const limitPrice = within(editOrder).getByLabelText('Price');
await userEvent.clear(limitPrice);
await userEvent.type(limitPrice, '0.111111');
const submitButton = within(editOrder).getByRole('button', {
name: 'Update',
@@ -24,7 +25,7 @@ describe('OrderEditDialog', () => {
await userEvent.click(submitButton);
const inputErrorText = within(editOrder).getByTestId('input-error-text');
expect(inputErrorText).toHaveTextContent(
'Price accepts up to 1 decimal places'
'Price must be a multiple of 0.1 for this market'
);
});
});
@@ -4,6 +4,8 @@ import {
addDecimal,
addDecimalsFormatNumber,
useValidateAmount,
determinePriceStep,
determineSizeStep,
} from '@vegaprotocol/utils';
import { Size } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
@@ -52,8 +54,10 @@ export const OrderEditDialog = ({
},
});
const step = toDecimal(order.market?.decimalPlaces ?? 0);
const stepSize = toDecimal(order.market?.positionDecimalPlaces ?? 0);
const step = order.market ? determinePriceStep(order.market) : toDecimal(0);
const stepSize = order.market
? determineSizeStep(order.market)
: toDecimal(0);
return (
<Dialog
@@ -117,9 +121,11 @@ export const OrderEditDialog = ({
required: t('You need to provide a price'),
validate: {
min: (value) =>
Number(value) > 0
Number(value) >= Number(step)
? true
: t('The price cannot be negative'),
: t('Price cannot be lower than {{step}}', {
step,
}),
validate: validateAmount(step, t('Price')),
},
})}
@@ -139,7 +145,11 @@ export const OrderEditDialog = ({
required: t('You need to provide a size'),
validate: {
min: (value) =>
Number(value) > 0 ? true : t('The size cannot be negative'),
Number(value) >= Number(stepSize)
? true
: t('Size cannot be lower than {{stepSize}}', {
stepSize,
}),
validate: validateAmount(stepSize, t('Size')),
},
})}
@@ -22,6 +22,7 @@ describe('OrderViewDialog', () => {
positionDecimalPlaces: 3,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
tickSize: '1',
fees: {
__typename: 'Fees',
factors: {
+1
View File
@@ -16,3 +16,4 @@ export * from './lib/validate';
export * from './lib/resolve-network-name';
export * from './lib/is-test-env';
export * from './lib/constants';
export * from './lib/step';
+32 -8
View File
@@ -18,18 +18,42 @@ export const getUnlimitedThreshold = (decimalPlaces: number) =>
const MIN_FRACTION_DIGITS = 2;
const MAX_FRACTION_DIGITS = 20;
export function toDecimal(numberOfDecimals: number) {
return new BigNumber(1)
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
.toString(10);
}
/**
* Converts "raw" value to a decimal representation as a `BigNumber`
*
* Example:
* `toBigNum(1, 3)` -> 0.001
* `toBigNum(1234, 2)` -> 12.34
*
* @param rawValue The "raw" value
* @param decimals The number of decimal places
*/
export function toBigNum(
rawValue: string | number | BigNumber,
decimals: number
): BigNumber {
const divides = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(rawValue || 0).dividedBy(divides);
const d = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(rawValue || 0).dividedBy(d);
}
/**
* Reverses `toBigNum` - converts given decimal representation
* as a "raw" value (`BigNumber`)
*
* Example:
* `formBigNum(5.4321, 4)` -> 54321
* `formBigNum(112.34, 2)` -> 11234
*/
export const fromBigNum = (
input: string | number | BigNumber,
decimals: number
) => {
const d = new BigNumber(10).exponentiatedBy(decimals);
return new BigNumber(input).times(d);
};
export function toDecimal(numberOfDecimals: number) {
return toBigNum(1, numberOfDecimals).toString(10);
}
export function addDecimal(
+21
View File
@@ -0,0 +1,21 @@
import type { Market } from '@vegaprotocol/types';
import { toBigNum, toDecimal } from './format';
export const determinePriceStep = (
market: Pick<Market, 'decimalPlaces' | 'tickSize'>
) => {
let priceStep = toDecimal(market.decimalPlaces);
const scaledTickSize = toBigNum(market.tickSize, market.decimalPlaces);
if (scaledTickSize.isGreaterThan(0)) {
priceStep = scaledTickSize.toString();
}
return priceStep;
};
export const determineSizeStep = (
market: Pick<Market, 'positionDecimalPlaces'>
) => {
return toDecimal(market.positionDecimalPlaces);
};
@@ -0,0 +1,38 @@
import { validateAgainstStep } from './validate-amount';
describe('validateAgainstStep', () => {
it('fails when step is an empty string', () => {
expect(validateAgainstStep('', '1234')).toEqual(false);
});
it.each([
[0, 0],
[1234567890, 0],
[0.03, 0.03],
[0.09, 0.03],
[0.27, 0.03],
[1, 1],
[123, 1],
[4, 2],
[8, 2],
])(
'checks whether given value (%s) IS a multiple of given step (%s)',
(value, step) => {
expect(validateAgainstStep(step, value)).toEqual(true);
}
);
it.each([
[1, 2],
[0.1, 0.003],
[1.11, 0.1],
[123.1, 1],
[222, 221],
[NaN, 1],
])(
'checks whether given value (%s) IS NOT a multiple of given step (%s)',
(value, step) => {
expect(validateAgainstStep(step, value)).toEqual(false);
}
);
});
+29 -19
View File
@@ -1,35 +1,24 @@
import { useCallback } from 'react';
import { useT } from '../use-t';
import BigNumber from 'bignumber.js';
export const useValidateAmount = () => {
const t = useT();
return useCallback(
(step: number | string, field: string) => {
const [, stepDecimals = ''] = String(step).split('.');
return (value?: string) => {
if (Number(step) > 1) {
if (Number(value) % Number(step) > 0) {
return t(
'{{field}} must be a multiple of {{step}} for this market',
{
field,
step,
}
);
}
return true;
}
const [, valueDecimals = ''] = (value || '').split('.');
if (stepDecimals.length < valueDecimals.length) {
if (stepDecimals === '') {
const isValid = validateAgainstStep(step, value);
if (!isValid) {
if (new BigNumber(step).isEqualTo(1)) {
return t('{{field}} must be whole numbers for this market', {
field,
step,
});
}
return t('{{field}} accepts up to {{decimals}} decimal places', {
return t('{{field}} must be a multiple of {{step}} for this market', {
field,
decimals: stepDecimals.length,
step,
});
}
return true;
@@ -38,3 +27,24 @@ export const useValidateAmount = () => {
[t]
);
};
const isMultipleOf = (value: BigNumber, multipleOf: BigNumber) =>
value.modulo(multipleOf).isZero();
export const validateAgainstStep = (
step: string | number,
input?: string | number
) => {
const stepValue = new BigNumber(step);
if (stepValue.isNaN()) {
// unable to check if step is not a number
return false;
}
if (stepValue.isZero()) {
// every number is valid when step is zero
return true;
}
const value = new BigNumber(input || '');
return isMultipleOf(value, stepValue);
};
@@ -140,6 +140,7 @@ describe('WithdrawFormContainer', () => {
positionDecimalPlaces: 0,
state: Types.MarketState.STATE_SUSPENDED,
tradingMode: Types.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
tickSize: '1',
fees: {
__typename: 'Fees',
factors: {