Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into develop

This commit is contained in:
Madalina Raicu
2022-10-28 11:03:07 +01:00
20 changed files with 496 additions and 74 deletions
@@ -1,15 +1,29 @@
import { Schema as Types } from '@vegaprotocol/types';
export const generatePartyMarketData = () => {
return {
party: {
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
accounts: [
{
type: 'General',
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '1200000',
asset: { id: 'fBTC', decimals: 5, __typename: 'Asset' },
market: null,
__typename: 'AccountBalance',
},
{
__typename: 'AccountBalance',
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '0.000000001',
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tUSD',
name: 'usd',
decimals: 0,
},
},
],
marginsConnection: { edges: null, __typename: 'MarginConnection' },
positionsConnection: { edges: null, __typename: 'PositionConnection' },
@@ -68,19 +68,20 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
const emptyString = ' - ';
const step = toDecimal(market.positionDecimalPlaces);
const order = watch();
const { message: invalidText, isDisabled } = useOrderValidation({
market,
orderType: order.type,
orderTimeInForce: order.timeInForce,
fieldErrors: errors,
});
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
const { pubKey } = useVegaWallet();
const estMargin = useOrderMargin({
order,
market,
partyId: pubKey || '',
});
const { message: invalidText, isDisabled } = useOrderValidation({
market,
orderType: order.type,
orderTimeInForce: order.timeInForce,
fieldErrors: errors,
estMargin,
});
const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit();
const { data: partyBalance } = usePartyBalanceQuery({
variables: { partyId: pubKey || '' },
+90 -33
View File
@@ -274,7 +274,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "95719.310664575736600945",
"locked_amount": "95541.63881894388350853",
"deposits": [
{
"amount": "129999.45",
@@ -340,7 +340,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "52600",
"total_removed": "0",
"locked_amount": "44745.36689497716662",
"locked_amount": "44637.43481735159618",
"deposits": [
{
"amount": "2600",
@@ -513,7 +513,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "4445.142694063927",
"locked_amount": "4434.88299086758",
"deposits": [
{
"amount": "5000",
@@ -724,7 +724,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "37113.928727744633569794",
"locked_amount": "36939.6491980608633088128",
"deposits": [
{
"amount": "97499.58",
@@ -757,7 +757,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "0",
"locked_amount": "50728.33311833605549421920164",
"locked_amount": "50490.123089363932785616473",
"deposits": [
{
"amount": "135173.4239508",
@@ -790,7 +790,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "15613.1888924949780453896",
"locked_amount": "15539.872504003513808234",
"deposits": [
{
"amount": "32499.86",
@@ -823,7 +823,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "5081.9414777083332611114",
"locked_amount": "5058.077704699755050653",
"deposits": [
{
"amount": "10833.29",
@@ -856,7 +856,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "22749.93",
"total_removed": "0",
"locked_amount": "18997.43227480366472295",
"locked_amount": "18908.224161420157492887",
"deposits": [
{
"amount": "6500",
@@ -931,7 +931,7 @@
"tranche_end": "2022-11-01T00:00:00.000Z",
"total_added": "30000",
"total_removed": "0",
"locked_amount": "732.94836956521836",
"locked_amount": "610.8355978260882",
"deposits": [
{
"amount": "7500",
@@ -1048,7 +1048,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "626880.6921411330574",
"locked_amount": "1155960.359757785506826668",
"locked_amount": "1151979.741877591455301096",
"deposits": [
{
"amount": "1852091.69",
@@ -1345,7 +1345,7 @@
"tranche_end": "2023-02-01T00:00:00.000Z",
"total_added": "42500",
"total_removed": "0",
"locked_amount": "22288.3435235507255",
"locked_amount": "22115.350430253619",
"deposits": [
{
"amount": "12500",
@@ -6070,7 +6070,7 @@
"tranche_start": "2021-09-03T00:00:00.000Z",
"tranche_end": "2022-09-03T00:00:00.000Z",
"total_added": "40726.000000000000000003",
"total_removed": "25366.75324099301",
"total_removed": "25378.75324099301",
"locked_amount": "0",
"deposits": [
{
@@ -12835,6 +12835,11 @@
"user": "0x38249D7DcC4eF7dFCa6579C069ADd3a1a4E4DeA3",
"tx": "0x77a11d777ff82ec042d23eaa2b57c27c2980b73f8c2d59e143bd366be803473d"
},
{
"amount": "12",
"user": "0x54e6627177165Aa7b6F31752c744c306FaC993dB",
"tx": "0xb6efb136c6521cd37238227a9a8f47c1b25785c675d69fcb529a275624c4360b"
},
{
"amount": "3.86333155832",
"user": "0xC6D7208DaDEe4F431bd0f3f11E7d4c91fF51bfb2",
@@ -20311,10 +20316,17 @@
"tx": "0x846ec1e97e805281e019842be187b58f63237c0ea04cc2abd002899e86762b39"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "12",
"user": "0x54e6627177165Aa7b6F31752c744c306FaC993dB",
"tranche_id": 11,
"tx": "0xb6efb136c6521cd37238227a9a8f47c1b25785c675d69fcb529a275624c4360b"
}
],
"total_tokens": "12",
"withdrawn_tokens": "0",
"remaining_tokens": "12"
"withdrawn_tokens": "12",
"remaining_tokens": "0"
},
{
"address": "0xcd6f7FBa16933469bd99F714CC802849216C532A",
@@ -24896,7 +24908,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "223655.6603333956257",
"locked_amount": "1800809.935025694503261770146",
"locked_amount": "1794693.11124778421066616842",
"deposits": [
{
"amount": "1998.95815",
@@ -25878,8 +25890,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "291604.2567888250208544",
"locked_amount": "11685243.9921930101885892916035514748661801",
"total_removed": "292058.2136868049826044",
"locked_amount": "11663554.1278141623085528184746161725790754",
"deposits": [
{
"amount": "16249.93",
@@ -26428,6 +26440,11 @@
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x4843b5deac41256835df3ead02d423c04d869f007e99f8a3a1ee40fb47892ab6"
},
{
"amount": "453.95689797996175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xa2f8867131a70c86f1a6ebe382bd3b6fa15acf5736e9a9634344695c58cbf071"
},
{
"amount": "243.932422174205",
"user": "0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0",
@@ -27286,6 +27303,12 @@
"tranche_id": 2,
"tx": "0x4843b5deac41256835df3ead02d423c04d869f007e99f8a3a1ee40fb47892ab6"
},
{
"amount": "453.95689797996175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xa2f8867131a70c86f1a6ebe382bd3b6fa15acf5736e9a9634344695c58cbf071"
},
{
"amount": "981.32183505687375",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -27996,8 +28019,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "68142.48983552186",
"remaining_tokens": "191856.39766447814"
"withdrawn_tokens": "68596.44673350182175",
"remaining_tokens": "191402.44076649817825"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -29645,8 +29668,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "3142112.555266197783780637",
"locked_amount": "5066297.416314942287083535753197734",
"total_removed": "3142743.615739270345670137",
"locked_amount": "5046273.47201148262856834879523159",
"deposits": [
{
"amount": "129284.449",
@@ -29880,6 +29903,11 @@
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x662f17a2fb66cfceca6f93bd9265c2def6bddb15893a38c5383e14307c65f9d0"
},
{
"amount": "631.0604730725618895",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x6fa8423fdeb8ed0784552a008fd9e9d40b59f76622a559aafdfc5d8401ad2272"
},
{
"amount": "1360.2331260920135955",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -32216,6 +32244,12 @@
"tranche_id": 3,
"tx": "0x662f17a2fb66cfceca6f93bd9265c2def6bddb15893a38c5383e14307c65f9d0"
},
{
"amount": "631.0604730725618895",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x6fa8423fdeb8ed0784552a008fd9e9d40b59f76622a559aafdfc5d8401ad2272"
},
{
"amount": "1360.2331260920135955",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -34204,8 +34238,8 @@
}
],
"total_tokens": "359123.469575",
"withdrawn_tokens": "233906.009470016341224",
"remaining_tokens": "125217.460104983658776"
"withdrawn_tokens": "234537.0699430889031135",
"remaining_tokens": "124586.3996319110968865"
},
{
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@@ -35323,7 +35357,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "2110079.858212660363479132",
"locked_amount": "1684821.346357521854252214981942818",
"locked_amount": "1676909.76485941822495456555758168",
"deposits": [
{
"amount": "552496.6455",
@@ -36941,8 +36975,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "6204.966976106296",
"locked_amount": "285348.62807326458926927321461188",
"total_removed": "6224.151146578296",
"locked_amount": "284379.38238039929630602147031964",
"deposits": [
{
"amount": "3000",
@@ -43601,6 +43635,11 @@
"user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A",
"tx": "0x241bc09d2972d7ea7c2eb18752e10305191bb94911837b64a860771a7890c014"
},
{
"amount": "19.184170472",
"user": "0x4F4A140E9B4a8403792970C6b5535ed0AA32c8A4",
"tx": "0x640d6905e3f009917c26c72a2ca24fc1cd17affc1706d75c0f1354cd6689ea18"
},
{
"amount": "95.248756976",
"user": "0xA17b91BdDcE741528fDDF8E5738A767B3053ac18",
@@ -58620,6 +58659,12 @@
}
],
"withdrawals": [
{
"amount": "19.184170472",
"user": "0x4F4A140E9B4a8403792970C6b5535ed0AA32c8A4",
"tranche_id": 5,
"tx": "0x640d6905e3f009917c26c72a2ca24fc1cd17affc1706d75c0f1354cd6689ea18"
},
{
"amount": "60.229674022",
"user": "0x4F4A140E9B4a8403792970C6b5535ed0AA32c8A4",
@@ -58628,8 +58673,8 @@
}
],
"total_tokens": "200",
"withdrawn_tokens": "60.229674022",
"remaining_tokens": "139.770325978"
"withdrawn_tokens": "79.413844494",
"remaining_tokens": "120.586155506"
},
{
"address": "0x4DA5acEc788beF3d7F67cEA1c3CfeE1a41c1A21A",
@@ -63920,7 +63965,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42",
"total_removed": "56344.8822826097989",
"total_removed": "56554.8822826097989",
"locked_amount": "0",
"deposits": [
{
@@ -68165,6 +68210,11 @@
"user": "0xbdaa56463cb068710Fc09A3481AdE4FbbAD0C46C",
"tx": "0x13d47be74806d8c713d741517f59d91198bc962b4e3c7083dea0574cda6178b9"
},
{
"amount": "210",
"user": "0xB0F161160E2766f6D042a47521e5b95A249f2293",
"tx": "0xb45c2382abb70681e3fb129fd4c52d8b142171445222cdd0977543f1eaa2bc91"
},
{
"amount": "40",
"user": "0x7cE6eB9eCcbe0E2d35Cd4515d58b813d943BF2Ec",
@@ -76161,10 +76211,17 @@
"tx": "0x057f65938b1360b6d2c4ebf5e789c67897cf60cb6a13f947004def03891afbd8"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "210",
"user": "0xB0F161160E2766f6D042a47521e5b95A249f2293",
"tranche_id": 6,
"tx": "0xb45c2382abb70681e3fb129fd4c52d8b142171445222cdd0977543f1eaa2bc91"
}
],
"total_tokens": "210",
"withdrawn_tokens": "0",
"remaining_tokens": "210"
"withdrawn_tokens": "210",
"remaining_tokens": "0"
},
{
"address": "0x82b6405BCf9F4C8579af1FaB37619C849A75EFAA",
@@ -3,7 +3,9 @@ import {
MarketTradingMode,
AuctionTrigger,
} from '@vegaprotocol/types';
import { generateEstimateOrder } from '../support/mocks/generate-fees';
import { connectVegaWallet } from '../support/vega-wallet';
import { aliasQuery } from '@vegaprotocol/cypress';
const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
@@ -201,7 +203,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
});
describe('deal ticket validation', { tags: '@smoke' }, () => {
before(() => {
beforeEach(() => {
cy.mockTradingPage();
cy.visit('/markets/market-0');
cy.wait('@Market');
@@ -441,3 +443,40 @@ describe('suspended market validation', { tags: '@regression' }, () => {
);
});
});
describe('margin required validation', { tags: '@regression' }, () => {
before(() => {
cy.mockTradingPage();
cy.mockGQL((req) => {
aliasQuery(
req,
'EstimateOrder',
generateEstimateOrder({
estimateOrder: {
marginLevels: { __typename: 'MarginLevels', initialLevel: '1000' },
},
})
);
});
cy.visit('/markets/market-0');
connectVegaWallet();
cy.wait('@Market');
});
it('should display info and button for deposit', () => {
cy.getByTestId('place-order').should('be.disabled');
cy.getByTestId('dealticket-error-message').should(
'contain.text',
"You don't have enough margin available to open this position"
);
cy.getByTestId('dealticket-error-message').should(
'contain.text',
'0.01000 tBTC currently required, 0.00100 tBTC available'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('dialog-content')
.find('h1')
.eq(0)
.should('have.text', 'Deposit');
});
});
@@ -0,0 +1,98 @@
import type { PartialDeep } from 'type-fest';
import merge from 'lodash/merge';
import { Schema as Types } from '@vegaprotocol/types';
import type {
EstimateOrderQuery,
MarketMarkPriceQuery,
PartyBalanceQuery,
PartyMarketDataQuery,
} from '@vegaprotocol/deal-ticket';
const estimateOrderMock: EstimateOrderQuery = {
estimateOrder: {
__typename: 'OrderEstimate',
totalFeeAmount: '0.0006',
fee: {
__typename: 'TradeFee',
makerFee: '0.0001',
infrastructureFee: '0.0002',
liquidityFee: '0.0003',
},
marginLevels: { __typename: 'MarginLevels', initialLevel: '1' },
},
};
export const generateEstimateOrder = (
override?: PartialDeep<EstimateOrderQuery>
) => {
return merge(estimateOrderMock, override);
};
const marketMarkPriceMock: MarketMarkPriceQuery = {
market: {
__typename: 'Market',
decimalPlaces: 5,
data: {
__typename: 'MarketData',
markPrice: '100',
market: { __typename: 'Market', id: 'market-0' },
},
},
};
export const generateMarkPrice = () => {
return marketMarkPriceMock;
};
const partyBalanceMock: PartyBalanceQuery = {
party: {
__typename: 'Party',
accounts: [
{
__typename: 'AccountBalance',
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100',
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
name: 'BTC',
decimals: 5,
},
},
],
},
};
export const generatePartyBalance = () => {
return partyBalanceMock;
};
export const generatePartyMarketData = (): PartyMarketDataQuery => {
return {
party: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
accounts: [
{
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '1200000',
asset: { id: 'fBTC', decimals: 5, __typename: 'Asset' },
market: null,
__typename: 'AccountBalance',
},
{
__typename: 'AccountBalance',
type: Types.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100',
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
decimals: 5,
},
},
],
marginsConnection: { edges: null, __typename: 'MarginConnection' },
__typename: 'Party',
},
};
};
+11
View File
@@ -22,6 +22,12 @@ import { generateMargins, generatePositions } from './mocks/generate-positions';
import { generateStatistics } from './mocks/generate-statistics';
import { generateTrades } from './mocks/generate-trades';
import { generateWithdrawals } from './mocks/generate-withdrawals';
import {
generateEstimateOrder,
generateMarkPrice,
generatePartyBalance,
generatePartyMarketData,
} from './mocks/generate-fees';
const mockTradingPage = (
req: CyHttpMessages.IncomingHttpRequest,
@@ -96,6 +102,11 @@ const mockTradingPage = (
aliasQuery(req, 'Candles', generateCandles());
aliasQuery(req, 'Withdrawals', generateWithdrawals());
aliasQuery(req, 'NetworkParamsQuery', generateNetworkParameters());
aliasQuery(req, 'EstimateOrder', generateEstimateOrder());
aliasQuery(req, 'MarketMarkPrice', generateMarkPrice());
aliasQuery(req, 'PartyBalance', generatePartyBalance());
aliasQuery(req, 'MarketPositions', generatePositions());
aliasQuery(req, 'PartyMarketData', generatePartyMarketData());
};
declare global {
+6
View File
@@ -60,6 +60,12 @@ export function createClient(base?: string) {
AccountUpdate: {
keyFields: false,
},
Party: {
keyFields: false,
},
Fees: {
keyFields: false,
},
},
});
+3
View File
@@ -41,6 +41,9 @@ function AppBody({ Component, pageProps }: AppProps) {
const [theme, toggleTheme] = useThemeSwitcher();
return (
<ThemeContext.Provider value={theme}>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Title />
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">
<AppLoader>
@@ -0,0 +1,47 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import { AccountType } from '@vegaprotocol/types';
import { toBigNum } from '@vegaprotocol/react-helpers';
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
import type { OrderMargin } from '../../hooks/use-order-margin';
import { usePartyBalanceQuery, useSettlementAccount } from '../../hooks';
interface Props {
market: DealTicketMarketFragment;
estMargin: OrderMargin | null;
}
export const useOrderMarginValidation = ({ market, estMargin }: Props) => {
const { pubKey } = useVegaWallet();
const { data: partyBalance } = usePartyBalanceQuery({
variables: { partyId: pubKey || '' },
skip: !pubKey,
fetchPolicy: 'no-cache',
});
const settlementAccount = useSettlementAccount(
market.tradableInstrument.instrument.product.settlementAsset.id,
partyBalance?.party?.accounts || [],
AccountType.ACCOUNT_TYPE_GENERAL
);
const balance = settlementAccount
? toBigNum(
settlementAccount.balance || 0,
settlementAccount.asset.decimals || 0
)
: toBigNum('0', 0);
const margin = toBigNum(estMargin?.margin || 0, 0);
const { id, symbol, decimals } =
market.tradableInstrument.instrument.product.settlementAsset;
if (balance.isZero() || balance.isLessThan(margin)) {
return {
balance: balance.toString(),
margin: margin.toString(),
id,
symbol,
decimals,
};
}
return false;
};
@@ -1,5 +1,7 @@
import * as React from 'react';
import { renderHook } from '@testing-library/react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import {
MarketState,
@@ -8,10 +10,11 @@ import {
Schema,
} from '@vegaprotocol/types';
import type { ValidationProps } from './use-order-validation';
import { marketTranslations } from './use-order-validation';
import { 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 * as OrderMarginValidation from './use-order-margin-validation';
import { ValidateMargin } from './validate-margin';
jest.mock('@vegaprotocol/wallet');
@@ -73,6 +76,15 @@ const defaultOrder = {
step: 0.1,
orderType: Schema.OrderType.TYPE_MARKET,
orderTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
estMargin: {
margin: '0,000001',
totalFees: '0,000006',
fees: {
makerFee: '0,000003',
liquidityFee: '0,000002',
infrastructureFee: '0,000001',
},
},
};
const ERROR = {
@@ -99,16 +111,29 @@ function setup(
) {
const mockUseVegaWallet = useVegaWallet as jest.Mock;
mockUseVegaWallet.mockReturnValue({ ...defaultWalletContext, context });
return renderHook(() => useOrderValidation({ ...defaultOrder, ...props }));
return renderHook(() => useOrderValidation({ ...defaultOrder, ...props }), {
wrapper: MockedProvider,
});
}
describe('useOrderValidation', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('Returns empty string when given valid data', () => {
jest
.spyOn(OrderMarginValidation, 'useOrderMarginValidation')
.mockReturnValue(false);
const { result } = setup();
expect(result.current).toStrictEqual({ isDisabled: false, message: `` });
});
it('Returns an error message when no keypair found', () => {
jest
.spyOn(OrderMarginValidation, 'useOrderMarginValidation')
.mockReturnValue(false);
const { result } = setup(defaultOrder, { pubKey: null });
expect(result.current).toStrictEqual({ isDisabled: false, message: `` });
});
@@ -239,4 +264,28 @@ describe('useOrderValidation', () => {
message: ERROR.FIELD_PRICE_STEP_DECIMAL,
});
});
it('Returns an error message when the estimated margin is higher than collateral', async () => {
const invalidatedMockValue = {
balance: '0000000,1',
margin: '000,1',
id: 'instrument-id',
symbol: 'asset-symbol',
decimals: 5,
};
jest
.spyOn(OrderMarginValidation, 'useOrderMarginValidation')
.mockReturnValue(invalidatedMockValue);
const { result } = setup({});
expect(result.current.isDisabled).toBe(true);
const testElement = <ValidateMargin {...invalidatedMockValue} />;
expect((result.current.message as React.ReactElement)?.props).toEqual(
testElement.props
);
expect((result.current.message as React.ReactElement)?.type).toEqual(
testElement.type
);
});
});
@@ -15,6 +15,9 @@ 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';
export const isMarketInAuction = (market: DealTicketMarketFragment) => {
return [
@@ -30,6 +33,7 @@ export type ValidationProps = {
orderType: Schema.OrderType;
orderTimeInForce: Schema.OrderTimeInForce;
fieldErrors?: FieldErrors<OrderSubmissionBody['orderSubmission']>;
estMargin: OrderMargin | null;
};
export const marketTranslations = (marketState: MarketState) => {
@@ -46,6 +50,7 @@ export const useOrderValidation = ({
fieldErrors = {},
orderType,
orderTimeInForce,
estMargin,
}: ValidationProps): {
message: React.ReactNode | string;
isDisabled: boolean;
@@ -53,6 +58,8 @@ export const useOrderValidation = ({
const { pubKey } = useVegaWallet();
const minSize = toDecimal(market.positionDecimalPlaces);
const isInvalidOrderMargin = useOrderMarginValidation({ market, estMargin });
const { message, isDisabled } = useMemo(() => {
if (!pubKey) {
return { message: t('No public key selected'), isDisabled: true };
@@ -266,6 +273,13 @@ export const useOrderValidation = ({
};
}
if (isInvalidOrderMargin) {
return {
isDisabled: true,
message: <ValidateMargin {...isInvalidOrderMargin} />,
};
}
if (
[
MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
@@ -291,6 +305,7 @@ export const useOrderValidation = ({
fieldErrors?.price?.type,
orderType,
orderTimeInForce,
isInvalidOrderMargin,
]);
return { message, isDisabled };
@@ -0,0 +1,53 @@
import React from 'react';
import { formatNumber, t } from '@vegaprotocol/react-helpers';
import { Button, Dialog } from '@vegaprotocol/ui-toolkit';
import { useState } from 'react';
import { DepositContainer } from '@vegaprotocol/deposits';
interface Props {
margin: string;
symbol: string;
id: string;
balance: string;
decimals: number;
}
export const ValidateMargin = ({
margin,
symbol,
id,
balance,
decimals,
}: Props) => {
const [depositDialog, setDepositDialog] = useState(false);
return (
<>
<div
className="flex flex-col center pb-3"
data-testid="deal-ticket-margin-invalidated"
>
<p>
{t("You don't have enough margin available to open this position.")}
</p>
<p>
{`${formatNumber(margin, decimals)} ${symbol} ${t(
'currently required'
)}, ${formatNumber(balance, decimals)} ${symbol} ${t('available')}`}
</p>
<Button
className="center mt-2"
variant="default"
size="xs"
data-testid="deal-ticket-deposit-dialog-button"
onClick={() => setDepositDialog(true)}
>
{t('Deposit')} {symbol}
</Button>
</div>
<Dialog open={depositDialog} onChange={setDepositDialog}>
<h1 className="text-2xl mb-4">{t('Deposit')}</h1>
<DepositContainer assetId={id} />
</Dialog>
</>
);
};
@@ -52,11 +52,16 @@ export const DealTicket = ({
defaultValues: getDefaultOrder(market),
});
const order = watch();
const feeDetails = useFeeDealTicketDetails(order, market);
const details = getFeeDetailsValues(feeDetails);
const { message, isDisabled: disabled } = useOrderValidation({
market,
orderType: order.type,
orderTimeInForce: order.timeInForce,
fieldErrors: errors,
estMargin: feeDetails.estMargin,
});
const isDisabled = transactionStatus === 'pending' || disabled;
@@ -101,9 +106,6 @@ export const DealTicket = ({
}
}, [marketPriceFormatted, order.type, setValue]);
const feeDetails = useFeeDealTicketDetails(order, market);
const details = getFeeDetailsValues(feeDetails);
return (
<form onSubmit={handleSubmit(onSubmit)} className="p-4" noValidate>
<Controller
+1
View File
@@ -1 +1,2 @@
import '@testing-library/jest-dom';
import 'jest-canvas-mock';
@@ -1,12 +1,13 @@
import { update } from './market-depth-provider';
import { captureException } from '@sentry/react';
const mockCaptureException = jest.fn();
const reload = jest.fn();
jest.mock('@sentry/react', () => {
const original = jest.requireActual('@sentry/react'); // Step 2.
return {
...original,
captureException: jest.fn(),
captureException: () => mockCaptureException(),
};
});
@@ -15,7 +16,10 @@ jest.mock('./orderbook-data', () => ({
}));
describe('market depth provider update', () => {
it('omits overlapping updates', () => {
beforeEach(() => {
mockCaptureException.mockClear();
});
it('omits not matching market', () => {
const data = {
id: '1',
depth: {
@@ -31,7 +35,7 @@ describe('market depth provider update', () => {
expect(updatedData).toBe(data);
});
it('omits not matching market', () => {
it('omits overlapping updates', () => {
const data = {
id: '1',
depth: {
@@ -52,6 +56,7 @@ describe('market depth provider update', () => {
];
expect(update(data, delta.slice(0, 1), reload)).toBe(data);
expect(update(data, delta.slice(1, 2), reload)).toBe(data);
expect(mockCaptureException).toBeCalledTimes(2);
});
it('restarts and captureException when there is gap in updates', () => {
@@ -71,6 +76,6 @@ describe('market depth provider update', () => {
const updatedData = update(data, delta, reload);
expect(updatedData).toBe(data);
expect(reload).toBeCalled();
expect(captureException).toBeCalled();
expect(mockCaptureException).toBeCalled();
});
});
@@ -23,13 +23,18 @@ export const update: Update<
if (delta.marketId !== data.id) {
continue;
}
if (Number(delta.sequenceNumber) <= Number(data.depth.sequenceNumber)) {
if (BigInt(delta.sequenceNumber) <= BigInt(data.depth.sequenceNumber)) {
captureException(
new Error(
`Sequence number from delta is lower or equal to last sequenceNumber for ${data.id}, ${delta.sequenceNumber} <= ${data.depth.sequenceNumber}, update skipped`
)
);
return data;
}
if (delta.previousSequenceNumber !== data.depth.sequenceNumber) {
captureException(
new Error(
`Sequence number gap in marketsDepthUpdate for {data.id}, {sequenceNumbers[delta.marketId]} - {delta.previousSequenceNumber}`
`Sequence number gap between delta previousSequenceNumber and stored sequence number in marketsDepthUpdate for market ${data.id}; ${delta.previousSequenceNumber} !== ${data.depth.sequenceNumber}, data provider reload`
)
);
reload();
@@ -1,5 +1,6 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { MarketExpires } from './market-expires';
describe('MarketExpires', () => {
@@ -13,7 +14,7 @@ describe('MarketExpires', () => {
'settlement:20220525T1200',
];
render(<MarketExpires tags={tags} />);
expect(screen.getByText('May 25')).toBeInTheDocument();
expect(screen.getByText('25/05/2022, 12:00:00')).toBeInTheDocument();
});
it('settlement-date:date', () => {
@@ -23,7 +24,7 @@ describe('MarketExpires', () => {
'settlement-date:2022-04-25T1200',
];
render(<MarketExpires tags={tags} />);
expect(screen.getByText('Apr 25')).toBeInTheDocument();
expect(screen.getByText('25/04/2022, 12:00:00')).toBeInTheDocument();
});
it('last one proper tag should matter', () => {
@@ -33,7 +34,7 @@ describe('MarketExpires', () => {
'settlement-expiry-date:2022-03-25T12:00:00',
];
render(<MarketExpires tags={tags} />);
expect(screen.getByText('Mar 25')).toBeInTheDocument();
expect(screen.getByText('25/03/2022, 12:00:00')).toBeInTheDocument();
});
it('when no proper tag nor date should be null', () => {
@@ -1,8 +1,7 @@
import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
import { getDateFormat, t } from '@vegaprotocol/react-helpers';
import { format, isValid, parseISO } from 'date-fns';
import { getDateTimeFormat, t } from '@vegaprotocol/react-helpers';
import { isValid, parseISO } from 'date-fns';
export const EXPIRE_DATE_FORMAT = 'MMM dd';
import type { SingleMarketFieldsFragment } from '@vegaprotocol/market-list';
export const getMarketExpiryDate = (
tags?: ReadonlyArray<string> | null
@@ -31,7 +30,7 @@ export const getMarketExpiryDateFormatted = (
): string | null => {
if (tags) {
const dateFound = getMarketExpiryDate(tags);
return dateFound ? format(dateFound, EXPIRE_DATE_FORMAT) : null;
return dateFound ? getDateTimeFormat().format(dateFound) : null;
}
return null;
};
@@ -45,14 +44,14 @@ export const getExpiryDate = (market: SingleMarketFieldsFragment): string => {
let content = null;
if (!closeDate) {
content = closedMarketDate
? `Expired on ${getDateFormat().format(closedMarketDate)}`
? `Expired on ${getDateTimeFormat().format(closedMarketDate)}`
: t('Not time-based');
} else {
const isExpired = Date.now() - closeDate.valueOf() > 0;
const expiryDate = getDateFormat().format(closeDate);
const expiryDate = getDateTimeFormat().format(closeDate);
if (isExpired) {
content = closedMarketDate
? `Expired on ${getDateFormat().format(closedMarketDate)}`
? `Expired on ${getDateTimeFormat().format(closedMarketDate)}`
: t('Expired');
} else {
content = expiryDate;
@@ -1,4 +1,4 @@
import { ethers } from 'ethers';
import { ethers, BigNumber } from 'ethers';
import abi from '../abis/multisig_abi.json';
export class MultisigControl {
@@ -14,11 +14,15 @@ export class MultisigControl {
}
add_signer(newSigner: string, nonce: string, signatures: string) {
return this.contract.add_signer(newSigner, nonce, signatures);
return this.contract.add_signer(
newSigner,
BigNumber.from(nonce),
signatures
);
}
burn_nonce(nonce: string, signatures: string) {
return this.contract.burn_nonce(nonce, signatures);
return this.contract.burn_nonce(BigNumber.from(nonce), signatures);
}
get_current_threshold() {
@@ -38,11 +42,19 @@ export class MultisigControl {
}
remove_signer(oldSigner: string, nonce: string, signatures: string) {
return this.contract.remove_signer(oldSigner, nonce, signatures);
return this.contract.remove_signer(
oldSigner,
BigNumber.from(nonce),
signatures
);
}
set_threshold(newThreshold: string, nonce: string, signatures: string) {
return this.contract.set_threshold(newThreshold, nonce, signatures);
return this.contract.set_threshold(
newThreshold,
BigNumber.from(nonce),
signatures
);
}
signers(address: string) {
@@ -50,6 +62,10 @@ export class MultisigControl {
}
verify_signatures(nonce: string, message: string, signatures: string) {
return this.contract.verify_signatures(nonce, message, signatures);
return this.contract.verify_signatures(
BigNumber.from(nonce),
message,
signatures
);
}
}
@@ -29,7 +29,7 @@ export function Dialog({
size = 'small',
}: DialogProps) {
const contentClasses = classNames(
'fixed top-0 left-0 z-20 flex justify-center items-start overflow-scroll',
'fixed top-0 left-0 z-20 flex justify-center items-start overflow-auto',
'w-full h-full'
);
const wrapperClasses = classNames(