feat(governance): successor market proposal details (#4393)

This commit is contained in:
Art 2023-07-26 14:52:12 +02:00 committed by GitHub
parent c3157de146
commit 278eb01c2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 312 additions and 32 deletions

View File

@ -25,3 +25,5 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
CYPRESS_FAIRGROUND=false
LC_ALL="en_US.UTF-8"
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true

View File

@ -25,3 +25,5 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
#Test configuration variables
CYPRESS_FAIRGROUND=false
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false

View File

@ -16,3 +16,6 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true

View File

@ -17,3 +17,6 @@ NX_VEGA_REST_URL=https://api.vega.community/api/v2/
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false

View File

@ -16,3 +16,6 @@ NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false

View File

@ -12,3 +12,6 @@ NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true

View File

@ -17,3 +17,6 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true

View File

@ -14,3 +14,6 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false

View File

@ -22,6 +22,16 @@ import {
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { FLAGS } from '@vegaprotocol/environment';
import { BrowserRouter } from 'react-router-dom';
jest.mock('@vegaprotocol/proposals', () => ({
...jest.requireActual('@vegaprotocol/proposals'),
useSuccessorMarketProposalDetails: () => ({
code: 'PARENT_CODE',
parentMarketId: 'PARENT_ID',
}),
}));
const renderComponent = (
proposal: ProposalQuery['proposal'],
@ -30,20 +40,27 @@ const renderComponent = (
) =>
render(
<AppStateProvider>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
networkParams={mockNetworkParams}
/>
</VegaWalletContext.Provider>
</MockedProvider>
<BrowserRouter>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
networkParams={mockNetworkParams}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</BrowserRouter>
</AppStateProvider>
);
describe('Proposal header', () => {
afterAll(() => {
jest.clearAllMocks();
});
it('Renders New market proposal', () => {
const mockedFlags = jest.mocked(FLAGS);
mockedFlags.SUCCESSOR_MARKETS = true;
renderComponent(
generateProposal({
rationale: {
@ -76,6 +93,9 @@ describe('Proposal header', () => {
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'tGBP settled future.'
);
expect(screen.getByTestId('proposal-successor-info')).toHaveTextContent(
'PARENT_CODE'
);
});
it('Renders Update market proposal', () => {

View File

@ -11,6 +11,10 @@ import { ProposalInfoLabel } from '../proposal-info-label';
import { useUserVote } from '../vote-details/use-user-vote';
import { ProposalVotingStatus } from '../proposal-voting-status';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import Routes from '../../../routes';
import { Link } from 'react-router-dom';
export const ProposalHeader = ({
proposal,
@ -39,6 +43,9 @@ export const ProposalHeader = ({
fallbackTitle = t('NewMarketProposal');
details = (
<>
{FLAGS.SUCCESSOR_MARKETS && (
<SuccessorCode proposalId={proposal?.id} />
)}
<span>
{t('Code')}: {change.instrument.code}.
</span>{' '}
@ -181,3 +188,20 @@ export const ProposalHeader = ({
</>
);
};
const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
const { t } = useTranslation();
const successor = useSuccessorMarketProposalDetails(proposalId);
return successor.parentMarketId || successor.code ? (
<span className="block" data-testid="proposal-successor-info">
{t('Successor market to')}:{' '}
<Link
to={`${Routes.PROPOSALS}/${successor.parentMarketId}`}
className="hover:underline"
>
{successor.code || successor.parentMarketId}
</Link>
</span>
) : null;
};

View File

@ -1,5 +1,6 @@
import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { MarketState } from '@vegaprotocol/types';
import compact from 'lodash/compact';
const accordionContent = 'accordion-content';
const blockExplorerLink = 'block-explorer-link';
@ -66,20 +67,24 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
// 6002-MDET-201
cy.getByTestId(marketTitle).contains('Key details').click();
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
validateMarketDataRow(1, 'Market ID', 'market-0');
const rows: [string, string][] = compact([
['Name', 'BTCUSD Monthly (30 Jun 2022)'],
['Market ID', 'market-0'],
Cypress.env('NX_SUCCESSOR_MARKETS') && ['Parent Market ID', 'PARENT-A'],
Cypress.env('NX_SUCCESSOR_MARKETS') && [
'Insurance Pool Fraction',
'0.75',
],
['Trading Mode', MarketTradingModeMapping.TRADING_MODE_CONTINUOUS],
['Market Decimal Places', '5'],
['Position Decimal Places', '0'],
['Settlement Asset Decimal Places', '5'],
]);
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
validateMarketDataRow(2, 'Parent Market ID', 'PARENT-A');
for (const rowNumber in rows) {
const [name, value] = rows[rowNumber];
validateMarketDataRow(Number(rowNumber), name, value);
}
validateMarketDataRow(
3,
'Trading Mode',
MarketTradingModeMapping.TRADING_MODE_CONTINUOUS
);
validateMarketDataRow(4, 'Market Decimal Places', '5');
validateMarketDataRow(5, 'Position Decimal Places', '0');
validateMarketDataRow(6, 'Settlement Asset Decimal Places', '5');
});
it('instrument displayed', () => {

View File

@ -36,6 +36,7 @@ import {
successorMarketQuery,
parentMarketIdQuery,
successorMarketIdsQuery,
successorMarketProposalDetailsQuery,
} from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
@ -186,6 +187,11 @@ const mockTradingPage = (
aliasGQLQuery(req, 'SuccessorMarket', successorMarketQuery());
aliasGQLQuery(req, 'ParentMarketId', parentMarketIdQuery());
aliasGQLQuery(req, 'SuccessorMarketIds', successorMarketIdsQuery());
aliasGQLQuery(
req,
'SuccessorMarketProposalDetails',
successorMarketProposalDetailsQuery()
);
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace

View File

@ -49,11 +49,6 @@ jest.mock('@vegaprotocol/markets', () => ({
: { data: undefined },
}));
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
FLAGS: { SUCCESSOR_MARKETS: true } as Partial<FeatureFlags>,
}));
jest.mock('@vegaprotocol/environment', () => {
const actual = jest.requireActual('@vegaprotocol/environment');
return {
@ -61,7 +56,7 @@ jest.mock('@vegaprotocol/environment', () => {
FLAGS: {
...actual.FLAGS,
SUCCESSOR_MARKETS: true,
},
} as FeatureFlags,
};
});

View File

@ -30,6 +30,7 @@ import { useOracleProofs } from '../../hooks';
import { OracleDialog } from '../oracle-dialog/oracle-dialog';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useParentMarketIdQuery } from '../../__generated__';
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
type MarketInfoProps = {
market: MarketInfo;
@ -144,6 +145,14 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
},
skip: !FLAGS.SUCCESSOR_MARKETS,
});
const { data: successor } = useSuccessorMarketProposalDetailsQuery({
variables: {
proposalId: market.proposal?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !market.proposal?.id,
});
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
@ -155,6 +164,11 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
name: market.tradableInstrument.instrument.name,
marketID: market.id,
parentMarketID: parentData?.market?.parentMarketID || '-',
insurancePoolFraction:
(successor?.proposal?.terms.change.__typename === 'NewMarket' &&
successor.proposal.terms.change.successorConfiguration
?.insurancePoolFraction) ||
'-',
tradingMode:
market.tradingMode &&
MarketTradingModeMapping[market.tradingMode],

View File

@ -102,5 +102,8 @@ export const tooltipMapping: Record<string, ReactNode> = {
`The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.`
),
suppliedStake: t('The current amount of liquidity supplied for this market.'),
parentMarketID: t('The ID of the market this market succeeds'),
parentMarketID: t('The ID of the market this market succeeds.'),
insurancePoolFraction: t(
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
),
};

View File

@ -5,6 +5,7 @@ import type {
import * as Schema from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import merge from 'lodash/merge';
import type { SuccessorMarketProposalDetailsQuery } from '../proposals-hooks';
export const proposalListQuery = (
override?: PartialDeep<ProposalsListQuery>
@ -1284,3 +1285,27 @@ const proposalListFields: ProposalListFieldsFragment[] = [
__typename: 'Proposal',
},
];
export const successorMarketProposalDetailsQuery = (
override?: SuccessorMarketProposalDetailsQuery
): SuccessorMarketProposalDetailsQuery =>
merge(
{
__typename: 'Query',
proposal: {
__typename: 'Proposal',
terms: {
__typename: 'ProposalTerms',
change: {
__typename: 'NewMarket',
successorConfiguration: {
__typename: 'SuccessorConfiguration',
insurancePoolFraction: '0.75',
parentMarketId: 'PARENT-A',
},
},
},
},
},
override
);

View File

@ -40,3 +40,30 @@ query ProposalOfMarket($marketId: ID!) {
}
}
}
query SuccessorMarketProposalDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on NewMarket {
successorConfiguration {
parentMarketId
insurancePoolFraction
}
}
}
}
}
}
query InstrumentDetails($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
code
name
}
}
}
}

View File

@ -27,6 +27,20 @@ export type ProposalOfMarketQueryVariables = Types.Exact<{
export type ProposalOfMarketQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null } } | null };
export type SuccessorMarketProposalDetailsQueryVariables = Types.Exact<{
proposalId: Types.Scalars['ID'];
}>;
export type SuccessorMarketProposalDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string, insurancePoolFraction: string } | null } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateNetworkParameter' } } } | null };
export type InstrumentDetailsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type InstrumentDetailsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', code: string, name: string } } } | null };
export const ProposalEventFieldsFragmentDoc = gql`
fragment ProposalEventFields on Proposal {
id
@ -148,3 +162,88 @@ export function useProposalOfMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookO
export type ProposalOfMarketQueryHookResult = ReturnType<typeof useProposalOfMarketQuery>;
export type ProposalOfMarketLazyQueryHookResult = ReturnType<typeof useProposalOfMarketLazyQuery>;
export type ProposalOfMarketQueryResult = Apollo.QueryResult<ProposalOfMarketQuery, ProposalOfMarketQueryVariables>;
export const SuccessorMarketProposalDetailsDocument = gql`
query SuccessorMarketProposalDetails($proposalId: ID!) {
proposal(id: $proposalId) {
id
terms {
change {
... on NewMarket {
successorConfiguration {
parentMarketId
insurancePoolFraction
}
}
}
}
}
}
`;
/**
* __useSuccessorMarketProposalDetailsQuery__
*
* To run a query within a React component, call `useSuccessorMarketProposalDetailsQuery` and pass it any options that fit your needs.
* When your component renders, `useSuccessorMarketProposalDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useSuccessorMarketProposalDetailsQuery({
* variables: {
* proposalId: // value for 'proposalId'
* },
* });
*/
export function useSuccessorMarketProposalDetailsQuery(baseOptions: Apollo.QueryHookOptions<SuccessorMarketProposalDetailsQuery, SuccessorMarketProposalDetailsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<SuccessorMarketProposalDetailsQuery, SuccessorMarketProposalDetailsQueryVariables>(SuccessorMarketProposalDetailsDocument, options);
}
export function useSuccessorMarketProposalDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SuccessorMarketProposalDetailsQuery, SuccessorMarketProposalDetailsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<SuccessorMarketProposalDetailsQuery, SuccessorMarketProposalDetailsQueryVariables>(SuccessorMarketProposalDetailsDocument, options);
}
export type SuccessorMarketProposalDetailsQueryHookResult = ReturnType<typeof useSuccessorMarketProposalDetailsQuery>;
export type SuccessorMarketProposalDetailsLazyQueryHookResult = ReturnType<typeof useSuccessorMarketProposalDetailsLazyQuery>;
export type SuccessorMarketProposalDetailsQueryResult = Apollo.QueryResult<SuccessorMarketProposalDetailsQuery, SuccessorMarketProposalDetailsQueryVariables>;
export const InstrumentDetailsDocument = gql`
query InstrumentDetails($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
code
name
}
}
}
}
`;
/**
* __useInstrumentDetailsQuery__
*
* To run a query within a React component, call `useInstrumentDetailsQuery` and pass it any options that fit your needs.
* When your component renders, `useInstrumentDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useInstrumentDetailsQuery({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useInstrumentDetailsQuery(baseOptions: Apollo.QueryHookOptions<InstrumentDetailsQuery, InstrumentDetailsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<InstrumentDetailsQuery, InstrumentDetailsQueryVariables>(InstrumentDetailsDocument, options);
}
export function useInstrumentDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<InstrumentDetailsQuery, InstrumentDetailsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<InstrumentDetailsQuery, InstrumentDetailsQueryVariables>(InstrumentDetailsDocument, options);
}
export type InstrumentDetailsQueryHookResult = ReturnType<typeof useInstrumentDetailsQuery>;
export type InstrumentDetailsLazyQueryHookResult = ReturnType<typeof useInstrumentDetailsLazyQuery>;
export type InstrumentDetailsQueryResult = Apollo.QueryResult<InstrumentDetailsQuery, InstrumentDetailsQueryVariables>;

View File

@ -3,3 +3,4 @@ export * from './use-proposal-event';
export * from './use-proposal-submit';
export * from './use-update-proposal';
export * from './use-update-network-paramaters-toasts';
export * from './use-successor-market-proposal-details';

View File

@ -0,0 +1,39 @@
import omit from 'lodash/omit';
import {
useInstrumentDetailsQuery,
useSuccessorMarketProposalDetailsQuery,
} from './__generated__/Proposal';
export const useSuccessorMarketProposalDetails = (
proposalId?: string | null
) => {
const { data: proposal } = useSuccessorMarketProposalDetailsQuery({
variables: {
proposalId: proposalId || '',
},
skip: !proposalId || proposalId.length === 0,
});
const successorDetails =
(proposal?.proposal &&
proposal.proposal?.terms.change.__typename === 'NewMarket' &&
proposal.proposal.terms.change.successorConfiguration) ||
undefined;
const { data: market } = useInstrumentDetailsQuery({
variables: {
marketId: successorDetails?.parentMarketId || '',
},
skip:
!successorDetails?.parentMarketId ||
successorDetails.parentMarketId.length === 0,
});
const details = {
...successorDetails,
code: market?.market?.tradableInstrument.instrument.code,
name: market?.market?.tradableInstrument.instrument.name,
};
return omit(details, '__typename');
};