feat(trading): do not show oracle banner when data is loading (#3821)

This commit is contained in:
Bartłomiej Głownia 2023-05-19 10:01:22 +02:00 committed by GitHub
parent 3be9126906
commit 9ce8907861
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 152 additions and 136 deletions

View File

@ -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;

View File

@ -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',
},
},
],

View File

@ -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;
}, [

View File

@ -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 (

View File

@ -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.'
),
};

View File

@ -0,0 +1 @@
export * from './oracle-dialog';

View File

@ -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>
);
};

View File

@ -1,2 +1 @@
export * from './oracle-full-profile.stories';
export * from './oracle-full-profile';

View File

@ -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';

View File

@ -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]);
});
});

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]);
};

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',
},
},
],
},
},
},