Compare commits

..
16 changed files with 168 additions and 252 deletions
@@ -194,7 +194,6 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
});
it('shows node health', function () {
// 0006-NETW-010
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId('node-health')
@@ -1,111 +0,0 @@
import { closeWelcomeDialog } from '../support/helpers';
const dialogContent = 'dialog-content';
const nodeHealth = 'node-health';
describe('home', { tags: '@regression' }, () => {
before(() => {
cy.clearLocalStorage();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
});
describe('footer', () => {
it('shows current block height', () => {
closeWelcomeDialog();
// 0006-NETW-004
// 0006-NETW-005
// 0006-NETW-008
// 0006-NETW-009
// 0006-NETW-011
cy.intercept('POST', 'http://localhost:3008/graphql', (req) => {
req.on('response', (res) => {
res.setDelay(3000);
});
});
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Warning delay ( >3 sec)');
cy.intercept('POST', 'http://localhost:3008/graphql', (req) => {
req.on('response', (res) => {
res.setDelay(1);
});
});
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Operational', { timeout: 10000 })
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.next()
.should('contain.text', '100'); // all mocked queries have x-block-height header set to 100
});
it('shows node switcher details', () => {
// 0006-NETW-012
// 0006-NETW-013
// 0006-NETW-014
// 0006-NETW-015
// 0006-NETW-016
cy.getByTestId(nodeHealth).click();
cy.getByTestId(dialogContent).should('contain.text', 'Connected node');
cy.getByTestId(dialogContent).should(
'contain.text',
'This app will only work on CUSTOM. Select a node to connect to.'
);
cy.getByTestId('node')
.first()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.next()
.should('contain.text', 'Response time')
.next()
.should('contain.text', 'Block')
.next()
.should('contain.text', 'Subscription');
cy.getByTestId('custom-row').should('contain.text', 'Other');
cy.getByTestId('dialog-close').click();
});
it('switch to other node', () => {
// 0006-NETW-017
// 0006-NETW-018
// 0006-NETW-019
// 0006-NETW-020
cy.getByTestId(nodeHealth).click();
cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click();
cy.getByTestId('connect').should('be.disabled');
cy.get("input[placeholder='https://']")
.focus()
.type(new URL(Cypress.env('VEGA_URL')).origin + '/graphql');
cy.getByTestId('connect').click();
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Operational');
});
});
describe('Network switcher', () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
});
// 0006-NETW-002
// 0006-NETW-003
it('switch to fairground network', () => {
cy.getByTestId('network-switcher').click();
cy.getByTestId('network-item').contains('Fairground testnet').click();
cy.get('[aria-haspopup="menu"]').should('contain.text', 'Fairground');
});
});
});
+16 -3
View File
@@ -4,7 +4,6 @@ import { marketsDataQuery } from '@vegaprotocol/mock';
import * as Schema from '@vegaprotocol/types';
const selectMarketOverlay = 'select-market-list';
const dialogContent = 'dialog-content';
const generateProposal = (code: string): ProposalListFieldsFragment => ({
__typename: 'Proposal',
@@ -289,7 +288,7 @@ describe('home', { tags: '@regression' }, () => {
cy.visit('/');
cy.wait('@Markets');
cy.location('hash').should('equal', '#/markets/market-1');
cy.getByTestId(dialogContent).should('not.exist');
cy.getByTestId('dialog-content').should('not.exist');
});
});
@@ -302,8 +301,22 @@ describe('home', { tags: '@regression' }, () => {
cy.visit('/');
cy.wait('@Markets');
cy.location('hash').should('equal', '#/markets/market-not-existing');
cy.getByTestId(dialogContent).should('not.exist');
cy.getByTestId('dialog-content').should('not.exist');
});
});
});
describe('footer', () => {
it('shows current block height', () => {
cy.visit('/');
cy.getByTestId('node-health')
.children()
.first()
.should('contain.text', 'Operational')
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.next()
.should('contain.text', '100'); // all mocked queries have x-block-height header set to 100
});
});
});
@@ -107,7 +107,6 @@ export const NetworkSwitcher = ({
onOpenChange={handleOpen}
trigger={
<DropdownMenuTrigger
data-testid="network-switcher"
ref={menuRef}
className={classNames(
'flex justify-between items-center text-sm text-vega-dark-600 dark:text-vega-light-600 py-1 px-2 rounded border border-vega-dark-200 whitespace-nowrap dark:hover:bg-vega-dark-500 hover:bg-vega-light-500',
@@ -5,7 +5,7 @@ import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { marketDataProvider } from '../../market-data-provider';
import { totalFeesPercentage } from '../../market-utils';
import { Dialog, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
formatNumber,
@@ -25,12 +25,9 @@ import { ConditionOperatorMapping } from '@vegaprotocol/types';
import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { useEnvironment } from '@vegaprotocol/environment';
import type { Provider } from '../../oracle-schema';
import {
OracleBasicProfile,
OracleProfileTitle,
OracleFullProfile,
} from '../../components';
import { useOracleProofs, useOracleMarkets } from '../../hooks';
import { OracleBasicProfile } from '../../components/oracle-basic-profile';
import { useOracleProofs } from '../../hooks';
import { OracleDialog } from '../oracle-dialog/oracle-dialog';
import { useDataProvider } from '@vegaprotocol/data-provider';
type PanelProps = Pick<
@@ -613,34 +610,6 @@ const NoOracleProof = ({
);
};
export const OracleDialog = ({
provider,
dataSourceSpecId,
open,
onChange,
}: {
dataSourceSpecId: string;
provider: Provider;
open: boolean;
onChange?: (isOpen: boolean) => void;
}) => {
const oracleMarkets = useOracleMarkets(provider);
return (
<Dialog
title={<OracleProfileTitle provider={provider} />}
aria-labelledby="oracle-proof-dialog"
open={open}
onChange={onChange}
>
<OracleFullProfile
provider={provider}
dataSourceSpecId={dataSourceSpecId}
markets={oracleMarkets}
/>
</Dialog>
);
};
const OracleProfile = (props: {
provider: Provider;
dataSourceSpecId: string;
@@ -143,7 +143,7 @@ export const marketInfoQuery = (
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
},
},
],
@@ -164,7 +164,7 @@ export const marketInfoQuery = (
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
},
},
],
@@ -1,8 +1,10 @@
import { useMemo } from 'react';
import { useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@blueprintjs/icons';
import { getMatchingOracleProvider, useOracleProofs } from '../../hooks';
import type { Market } from '../../markets-provider';
import { getVerifiedStatusIcon } from '../oracle-basic-profile';
export const OracleStatus = ({
dataSourceSpecForSettlementData,
@@ -23,22 +25,15 @@ export const OracleStatus = ({
dataSourceSpecForTradingTermination.data,
providers
);
if (
(settlementDataProvider &&
settlementDataProvider.oracle.status !== 'GOOD') ||
(tradingTerminationDataProvider &&
tradingTerminationDataProvider.oracle.status !== 'GOOD')
) {
return (
<span
className="ml-1"
role="img"
aria-label={t('oracle status not healthy')}
>
</span>
);
let maliciousOracleProvider = null;
if (settlementDataProvider?.oracle.status !== 'GOOD') {
maliciousOracleProvider = settlementDataProvider;
} else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
maliciousOracleProvider = tradingTerminationDataProvider;
}
if (!maliciousOracleProvider) return null;
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
return <Icon size={3} name={icon as IconName} className="ml-1" />;
}
return null;
}, [
@@ -6,27 +6,13 @@ import {
NotificationBanner,
ButtonLink,
} from '@vegaprotocol/ui-toolkit';
import { OracleDialog } from '../market-info';
export const oracleStatuses = {
UNKNOWN: t(
"This public key's proofs have not been verified yet, or no proofs have been provided yet."
),
GOOD: t("This public key's proofs have been verified."),
SUSPICIOUS: t(
'This public key is suspected to be acting in bad faith, pending investigation.'
),
MALICIOUS: t('This public key has been observed acting in bad faith.'),
RETIRED: t('This public key is no longer in use.'),
COMPROMISED: t(
'This public key is no longer in the control of its original owners.'
),
};
import { OracleDialog } from '../oracle-dialog';
import { oracleStatuses } from './oracle-statuses';
export const OracleBanner = ({ marketId }: { marketId: string }) => {
const [open, onChange] = useState(false);
const settlementOracle = useMarketOracle(marketId);
const tradingTerminationOracle = useMarketOracle(
const { data: settlementOracle } = useMarketOracle(marketId);
const { data: tradingTerminationOracle } = useMarketOracle(
marketId,
'dataSourceSpecForTradingTermination'
);
@@ -36,15 +22,7 @@ export const OracleBanner = ({ marketId }: { marketId: string }) => {
} else if (tradingTerminationOracle?.provider.oracle.status !== 'GOOD') {
maliciousOracle = tradingTerminationOracle;
}
if (!maliciousOracle) return null;
if (!settlementOracle && !tradingTerminationOracle) {
return (
<NotificationBanner intent={Intent.Primary}>
<div>{t('There is no oracle for this market.')} </div>
</NotificationBanner>
);
}
const { provider } = maliciousOracle;
return (
@@ -0,0 +1,16 @@
import { t } from '@vegaprotocol/i18n';
export const oracleStatuses = {
UNKNOWN: t(
"This public key's proofs have not been verified yet, or no proofs have been provided yet."
),
GOOD: t("This public key's proofs have been verified."),
SUSPICIOUS: t(
'This public key is suspected to be acting in bad faith, pending investigation.'
),
MALICIOUS: t('This public key has been observed acting in bad faith.'),
RETIRED: t('This public key is no longer in use.'),
COMPROMISED: t(
'This public key is no longer in the control of its original owners.'
),
};
@@ -0,0 +1 @@
export * from './oracle-dialog';
@@ -0,0 +1,35 @@
import { Dialog } from '@vegaprotocol/ui-toolkit';
import {
OracleProfileTitle,
OracleFullProfile,
} from '../../components/oracle-full-profile';
import { useOracleMarkets } from '../../hooks';
import type { Provider } from '../../oracle-schema';
export const OracleDialog = ({
provider,
dataSourceSpecId,
open,
onChange,
}: {
dataSourceSpecId: string;
provider: Provider;
open: boolean;
onChange?: (isOpen: boolean) => void;
}) => {
const oracleMarkets = useOracleMarkets(provider);
return (
<Dialog
title={<OracleProfileTitle provider={provider} />}
aria-labelledby="oracle-proof-dialog"
open={open}
onChange={onChange}
>
<OracleFullProfile
provider={provider}
dataSourceSpecId={dataSourceSpecId}
markets={oracleMarkets}
/>
</Dialog>
);
};
@@ -1,2 +1 @@
export * from './oracle-full-profile.stories';
export * from './oracle-full-profile';
@@ -9,7 +9,7 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { oracleStatuses } from '../oracle-banner';
import { oracleStatuses } from '../oracle-banner/oracle-statuses';
import type { IconName } from '@blueprintjs/icons';
import classNames from 'classnames';
import { getLinkIcon, getVerifiedStatusIcon } from '../oracle-basic-profile';
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react';
import { useMarketOracle } from './use-market-oracle';
import type { MarketInfoQuery } from '../components/market-info/__generated__/MarketInfo';
import type { MarketFieldsFragment } from '../__generated__/markets';
import type { Provider } from '../oracle-schema';
const ORACLE_PROOFS_URL = 'ORACLE_PROOFS_URL';
@@ -10,43 +10,42 @@ const key = 'key';
const dataSourceSpecId = 'dataSourceSpecId';
const mockEnvironment = jest.fn(() => ({ ORACLE_PROOFS_URL }));
const mockDataProvider = jest.fn<
{ data: MarketInfoQuery['market'] },
unknown[]
>(() => ({
data: {
tradableInstrument: {
instrument: {
product: {
dataSourceSpecForSettlementData: {
id: dataSourceSpecId,
data: {
sourceType: {
__typename: 'DataSourceDefinitionExternal',
const mockMarket = jest.fn<{ data: MarketFieldsFragment | null }, unknown[]>(
() => ({
data: {
tradableInstrument: {
instrument: {
product: {
dataSourceSpecForSettlementData: {
id: dataSourceSpecId,
data: {
sourceType: {
signers: [
{
signer: {
__typename: 'ETHAddress',
address,
__typename: 'DataSourceDefinitionExternal',
sourceType: {
signers: [
{
signer: {
__typename: 'ETHAddress',
address,
},
},
},
{
signer: {
__typename: 'PubKey',
key,
{
signer: {
__typename: 'PubKey',
key,
},
},
},
],
],
},
},
},
},
},
},
},
},
} as MarketInfoQuery['market'],
}));
} as MarketFieldsFragment,
})
);
const mockOracleProofs = jest.fn<{ data?: Provider[] }, unknown[]>(() => ({}));
@@ -54,9 +53,8 @@ jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: jest.fn((args) => mockEnvironment()),
}));
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn((args) => mockDataProvider()),
jest.mock('../markets-provider', () => ({
useMarket: jest.fn((args) => mockMarket()),
}));
jest.mock('./use-oracle-proofs', () => ({
@@ -66,15 +64,15 @@ jest.mock('./use-oracle-proofs', () => ({
const marketId = 'marketId';
describe('useMarketOracle', () => {
it('returns undefined if no market info present', () => {
mockDataProvider.mockReturnValueOnce({ data: null });
mockMarket.mockReturnValueOnce({ data: null });
const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current).toBeUndefined();
expect(result.current?.data).toBeUndefined();
});
it('returns undefined if no oracle proofs present', () => {
mockOracleProofs.mockReturnValueOnce({ data: undefined });
const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current).toBeUndefined();
expect(result.current?.data).toBeUndefined();
});
it('returns oracle matched by eth_address', () => {
@@ -102,8 +100,8 @@ describe('useMarketOracle', () => {
data,
});
const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current?.dataSourceSpecId).toBe(dataSourceSpecId);
expect(result.current?.provider).toBe(data[1]);
expect(result.current?.data?.dataSourceSpecId).toBe(dataSourceSpecId);
expect(result.current?.data?.provider).toBe(data[1]);
});
it('returns oracle matching by public_key', () => {
@@ -131,7 +129,7 @@ describe('useMarketOracle', () => {
data,
});
const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current?.dataSourceSpecId).toBe(dataSourceSpecId);
expect(result.current?.provider).toBe(data[1]);
expect(result.current?.data?.dataSourceSpecId).toBe(dataSourceSpecId);
expect(result.current?.data?.provider).toBe(data[1]);
});
});
+21 -14
View File
@@ -1,7 +1,7 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { useOracleProofs } from './use-oracle-proofs';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketInfoProvider } from '../components/market-info/market-info-data-provider';
import { useMarket } from '../markets-provider';
import { useMemo } from 'react';
import type { Provider } from '../oracle-schema';
import type { DataSourceSpecFragment } from '../__generated__/OracleMarketsSpec';
@@ -41,23 +41,30 @@ export const useMarketOracle = (
dataSourceType:
| 'dataSourceSpecForSettlementData'
| 'dataSourceSpecForTradingTermination' = 'dataSourceSpecForSettlementData'
) => {
): {
data?: {
provider: NonNullable<ReturnType<typeof getMatchingOracleProvider>>;
dataSourceSpecId: string;
};
loading?: boolean;
} => {
const { ORACLE_PROOFS_URL } = useEnvironment();
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: { marketId },
});
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
const { data: market, loading: marketLoading } = useMarket(marketId);
const { data: providers, loading: providersLoading } =
useOracleProofs(ORACLE_PROOFS_URL);
return useMemo(() => {
if (!providers || !marketInfo) {
return undefined;
if (marketLoading || providersLoading) {
return { loading: true };
}
if (!providers || !market) {
return { data: undefined };
}
const dataSourceSpec =
marketInfo.tradableInstrument.instrument.product[dataSourceType];
market.tradableInstrument.instrument.product[dataSourceType];
const provider = getMatchingOracleProvider(dataSourceSpec.data, providers);
if (provider) {
return { provider, dataSourceSpecId: dataSourceSpec.id };
return { data: { provider, dataSourceSpecId: dataSourceSpec.id } };
}
return undefined;
}, [marketInfo, dataSourceType, providers]);
return { data: undefined };
}, [market, dataSourceType, providers, marketLoading, providersLoading]);
};
+20 -2
View File
@@ -64,26 +64,44 @@ export const createMarketFragment = (
},
dataSourceSpecForTradingTermination: {
__typename: 'DataSourceSpec',
id: 'oracleId',
id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
data: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
},
},
],
},
},
},
},
dataSourceSpecForSettlementData: {
__typename: 'DataSourceSpec',
id: 'oracleId',
id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
data: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: {
__typename: 'DataSourceSpecConfiguration',
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
},
},
],
},
},
},