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 { t } from '@vegaprotocol/i18n';
import { marketDataProvider } from '../../market-data-provider'; import { marketDataProvider } from '../../market-data-provider';
import { totalFeesPercentage } from '../../market-utils'; import { totalFeesPercentage } from '../../market-utils';
import { Dialog, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit'; import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
formatNumber, formatNumber,
@ -25,12 +25,9 @@ import { ConditionOperatorMapping } from '@vegaprotocol/types';
import { MarketTradingModeMapping } from '@vegaprotocol/types'; import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import type { Provider } from '../../oracle-schema'; import type { Provider } from '../../oracle-schema';
import { import { OracleBasicProfile } from '../../components/oracle-basic-profile';
OracleBasicProfile, import { useOracleProofs } from '../../hooks';
OracleProfileTitle, import { OracleDialog } from '../oracle-dialog/oracle-dialog';
OracleFullProfile,
} from '../../components';
import { useOracleProofs, useOracleMarkets } from '../../hooks';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
type PanelProps = Pick< 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: { const OracleProfile = (props: {
provider: Provider; provider: Provider;
dataSourceSpecId: string; dataSourceSpecId: string;

View File

@ -143,7 +143,7 @@ export const marketInfoQuery = (
__typename: 'Signer', __typename: 'Signer',
signer: { signer: {
__typename: 'PubKey', __typename: 'PubKey',
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
}, },
}, },
], ],
@ -164,7 +164,7 @@ export const marketInfoQuery = (
__typename: 'Signer', __typename: 'Signer',
signer: { signer: {
__typename: 'PubKey', __typename: 'PubKey',
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f', key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
}, },
}, },
], ],

View File

@ -1,8 +1,10 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useEnvironment } from '@vegaprotocol/environment'; 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 { getMatchingOracleProvider, useOracleProofs } from '../../hooks';
import type { Market } from '../../markets-provider'; import type { Market } from '../../markets-provider';
import { getVerifiedStatusIcon } from '../oracle-basic-profile';
export const OracleStatus = ({ export const OracleStatus = ({
dataSourceSpecForSettlementData, dataSourceSpecForSettlementData,
@ -23,22 +25,15 @@ export const OracleStatus = ({
dataSourceSpecForTradingTermination.data, dataSourceSpecForTradingTermination.data,
providers providers
); );
if ( let maliciousOracleProvider = null;
(settlementDataProvider && if (settlementDataProvider?.oracle.status !== 'GOOD') {
settlementDataProvider.oracle.status !== 'GOOD') || maliciousOracleProvider = settlementDataProvider;
(tradingTerminationDataProvider && } else if (tradingTerminationDataProvider?.oracle.status !== 'GOOD') {
tradingTerminationDataProvider.oracle.status !== 'GOOD') maliciousOracleProvider = tradingTerminationDataProvider;
) {
return (
<span
className="ml-1"
role="img"
aria-label={t('oracle status not healthy')}
>
</span>
);
} }
if (!maliciousOracleProvider) return null;
const { icon } = getVerifiedStatusIcon(maliciousOracleProvider);
return <Icon size={3} name={icon as IconName} className="ml-1" />;
} }
return null; return null;
}, [ }, [

View File

@ -6,27 +6,13 @@ import {
NotificationBanner, NotificationBanner,
ButtonLink, ButtonLink,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { OracleDialog } from '../market-info'; import { OracleDialog } from '../oracle-dialog';
import { oracleStatuses } from './oracle-statuses';
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.'
),
};
export const OracleBanner = ({ marketId }: { marketId: string }) => { export const OracleBanner = ({ marketId }: { marketId: string }) => {
const [open, onChange] = useState(false); const [open, onChange] = useState(false);
const settlementOracle = useMarketOracle(marketId); const { data: settlementOracle } = useMarketOracle(marketId);
const tradingTerminationOracle = useMarketOracle( const { data: tradingTerminationOracle } = useMarketOracle(
marketId, marketId,
'dataSourceSpecForTradingTermination' 'dataSourceSpecForTradingTermination'
); );
@ -36,15 +22,7 @@ export const OracleBanner = ({ marketId }: { marketId: string }) => {
} else if (tradingTerminationOracle?.provider.oracle.status !== 'GOOD') { } else if (tradingTerminationOracle?.provider.oracle.status !== 'GOOD') {
maliciousOracle = tradingTerminationOracle; maliciousOracle = tradingTerminationOracle;
} }
if (!maliciousOracle) return null; 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; const { provider } = maliciousOracle;
return ( 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'; export * from './oracle-full-profile';

View File

@ -9,7 +9,7 @@ import {
VegaIcon, VegaIcon,
VegaIconNames, VegaIconNames,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { oracleStatuses } from '../oracle-banner'; import { oracleStatuses } from '../oracle-banner/oracle-statuses';
import type { IconName } from '@blueprintjs/icons'; import type { IconName } from '@blueprintjs/icons';
import classNames from 'classnames'; import classNames from 'classnames';
import { getLinkIcon, getVerifiedStatusIcon } from '../oracle-basic-profile'; import { getLinkIcon, getVerifiedStatusIcon } from '../oracle-basic-profile';

View File

@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { useMarketOracle } from './use-market-oracle'; 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'; import type { Provider } from '../oracle-schema';
const ORACLE_PROOFS_URL = 'ORACLE_PROOFS_URL'; const ORACLE_PROOFS_URL = 'ORACLE_PROOFS_URL';
@ -10,43 +10,42 @@ const key = 'key';
const dataSourceSpecId = 'dataSourceSpecId'; const dataSourceSpecId = 'dataSourceSpecId';
const mockEnvironment = jest.fn(() => ({ ORACLE_PROOFS_URL })); const mockEnvironment = jest.fn(() => ({ ORACLE_PROOFS_URL }));
const mockDataProvider = jest.fn< const mockMarket = jest.fn<{ data: MarketFieldsFragment | null }, unknown[]>(
{ data: MarketInfoQuery['market'] }, () => ({
unknown[] data: {
>(() => ({ tradableInstrument: {
data: { instrument: {
tradableInstrument: { product: {
instrument: { dataSourceSpecForSettlementData: {
product: { id: dataSourceSpecId,
dataSourceSpecForSettlementData: { data: {
id: dataSourceSpecId,
data: {
sourceType: {
__typename: 'DataSourceDefinitionExternal',
sourceType: { sourceType: {
signers: [ __typename: 'DataSourceDefinitionExternal',
{ sourceType: {
signer: { signers: [
__typename: 'ETHAddress', {
address, signer: {
__typename: 'ETHAddress',
address,
},
}, },
}, {
{ signer: {
signer: { __typename: 'PubKey',
__typename: 'PubKey', key,
key, },
}, },
}, ],
], },
}, },
}, },
}, },
}, },
}, },
}, },
}, } as MarketFieldsFragment,
} as MarketInfoQuery['market'], })
})); );
const mockOracleProofs = jest.fn<{ data?: Provider[] }, unknown[]>(() => ({})); const mockOracleProofs = jest.fn<{ data?: Provider[] }, unknown[]>(() => ({}));
@ -54,9 +53,8 @@ jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: jest.fn((args) => mockEnvironment()), useEnvironment: jest.fn((args) => mockEnvironment()),
})); }));
jest.mock('@vegaprotocol/data-provider', () => ({ jest.mock('../markets-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'), useMarket: jest.fn((args) => mockMarket()),
useDataProvider: jest.fn((args) => mockDataProvider()),
})); }));
jest.mock('./use-oracle-proofs', () => ({ jest.mock('./use-oracle-proofs', () => ({
@ -66,15 +64,15 @@ jest.mock('./use-oracle-proofs', () => ({
const marketId = 'marketId'; const marketId = 'marketId';
describe('useMarketOracle', () => { describe('useMarketOracle', () => {
it('returns undefined if no market info present', () => { it('returns undefined if no market info present', () => {
mockDataProvider.mockReturnValueOnce({ data: null }); mockMarket.mockReturnValueOnce({ data: null });
const { result } = renderHook(() => useMarketOracle(marketId)); const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current).toBeUndefined(); expect(result.current?.data).toBeUndefined();
}); });
it('returns undefined if no oracle proofs present', () => { it('returns undefined if no oracle proofs present', () => {
mockOracleProofs.mockReturnValueOnce({ data: undefined }); mockOracleProofs.mockReturnValueOnce({ data: undefined });
const { result } = renderHook(() => useMarketOracle(marketId)); const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current).toBeUndefined(); expect(result.current?.data).toBeUndefined();
}); });
it('returns oracle matched by eth_address', () => { it('returns oracle matched by eth_address', () => {
@ -102,8 +100,8 @@ describe('useMarketOracle', () => {
data, data,
}); });
const { result } = renderHook(() => useMarketOracle(marketId)); const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current?.dataSourceSpecId).toBe(dataSourceSpecId); expect(result.current?.data?.dataSourceSpecId).toBe(dataSourceSpecId);
expect(result.current?.provider).toBe(data[1]); expect(result.current?.data?.provider).toBe(data[1]);
}); });
it('returns oracle matching by public_key', () => { it('returns oracle matching by public_key', () => {
@ -131,7 +129,7 @@ describe('useMarketOracle', () => {
data, data,
}); });
const { result } = renderHook(() => useMarketOracle(marketId)); const { result } = renderHook(() => useMarketOracle(marketId));
expect(result.current?.dataSourceSpecId).toBe(dataSourceSpecId); expect(result.current?.data?.dataSourceSpecId).toBe(dataSourceSpecId);
expect(result.current?.provider).toBe(data[1]); expect(result.current?.data?.provider).toBe(data[1]);
}); });
}); });

View File

@ -1,7 +1,7 @@
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { useOracleProofs } from './use-oracle-proofs'; import { useOracleProofs } from './use-oracle-proofs';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useMarket } from '../markets-provider';
import { marketInfoProvider } from '../components/market-info/market-info-data-provider';
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { Provider } from '../oracle-schema'; import type { Provider } from '../oracle-schema';
import type { DataSourceSpecFragment } from '../__generated__/OracleMarketsSpec'; import type { DataSourceSpecFragment } from '../__generated__/OracleMarketsSpec';
@ -41,23 +41,30 @@ export const useMarketOracle = (
dataSourceType: dataSourceType:
| 'dataSourceSpecForSettlementData' | 'dataSourceSpecForSettlementData'
| 'dataSourceSpecForTradingTermination' = 'dataSourceSpecForSettlementData' | 'dataSourceSpecForTradingTermination' = 'dataSourceSpecForSettlementData'
) => { ): {
data?: {
provider: NonNullable<ReturnType<typeof getMatchingOracleProvider>>;
dataSourceSpecId: string;
};
loading?: boolean;
} => {
const { ORACLE_PROOFS_URL } = useEnvironment(); const { ORACLE_PROOFS_URL } = useEnvironment();
const { data: marketInfo } = useDataProvider({ const { data: market, loading: marketLoading } = useMarket(marketId);
dataProvider: marketInfoProvider, const { data: providers, loading: providersLoading } =
variables: { marketId }, useOracleProofs(ORACLE_PROOFS_URL);
});
const { data: providers } = useOracleProofs(ORACLE_PROOFS_URL);
return useMemo(() => { return useMemo(() => {
if (!providers || !marketInfo) { if (marketLoading || providersLoading) {
return undefined; return { loading: true };
}
if (!providers || !market) {
return { data: undefined };
} }
const dataSourceSpec = const dataSourceSpec =
marketInfo.tradableInstrument.instrument.product[dataSourceType]; market.tradableInstrument.instrument.product[dataSourceType];
const provider = getMatchingOracleProvider(dataSourceSpec.data, providers); const provider = getMatchingOracleProvider(dataSourceSpec.data, providers);
if (provider) { if (provider) {
return { provider, dataSourceSpecId: dataSourceSpec.id }; return { data: { provider, dataSourceSpecId: dataSourceSpec.id } };
} }
return undefined; return { data: undefined };
}, [marketInfo, dataSourceType, providers]); }, [market, dataSourceType, providers, marketLoading, providersLoading]);
}; };

View File

@ -64,26 +64,44 @@ export const createMarketFragment = (
}, },
dataSourceSpecForTradingTermination: { dataSourceSpecForTradingTermination: {
__typename: 'DataSourceSpec', __typename: 'DataSourceSpec',
id: 'oracleId', id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
data: { data: {
__typename: 'DataSourceDefinition', __typename: 'DataSourceDefinition',
sourceType: { sourceType: {
__typename: 'DataSourceDefinitionExternal', __typename: 'DataSourceDefinitionExternal',
sourceType: { sourceType: {
__typename: 'DataSourceSpecConfiguration', __typename: 'DataSourceSpecConfiguration',
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
},
},
],
}, },
}, },
}, },
}, },
dataSourceSpecForSettlementData: { dataSourceSpecForSettlementData: {
__typename: 'DataSourceSpec', __typename: 'DataSourceSpec',
id: 'oracleId', id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
data: { data: {
__typename: 'DataSourceDefinition', __typename: 'DataSourceDefinition',
sourceType: { sourceType: {
__typename: 'DataSourceDefinitionExternal', __typename: 'DataSourceDefinitionExternal',
sourceType: { sourceType: {
__typename: 'DataSourceSpecConfiguration', __typename: 'DataSourceSpecConfiguration',
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
},
},
],
}, },
}, },
}, },