feat(trading): oracle banner check termination and settlement data oracles (#3707)

This commit is contained in:
m.ray 2023-05-16 16:12:22 +03:00 committed by GitHub
parent 1e9ff31fd7
commit 1a274a67c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 13 deletions

View File

@ -199,7 +199,6 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
.within(() => { .within(() => {
cy.getByTestId('block-explorer-link').contains('Block explorer'); cy.getByTestId('block-explorer-link').contains('Block explorer');
cy.getByTestId('github-link').contains('Oracle repository'); cy.getByTestId('github-link').contains('Oracle repository');
cy.getByTestId('verified-accounts').contains('0 proofs of ownership');
}); });
cy.getByTestId('dialog-close').click(); cy.getByTestId('dialog-close').click();

View File

@ -25,14 +25,30 @@ export const oracleStatuses = {
export const OracleBanner = ({ marketId }: { marketId: string }) => { export const OracleBanner = ({ marketId }: { marketId: string }) => {
const [open, onChange] = useState(false); const [open, onChange] = useState(false);
const oracle = useMarketOracle(marketId); const settlementOracle = useMarketOracle(marketId);
if (!oracle || oracle.provider.oracle.status === 'GOOD') { const tradingTerminationOracle = useMarketOracle(
return null; marketId,
'dataSourceSpecForTradingTermination'
);
let maliciousOracle = null;
if (settlementOracle?.provider.oracle.status !== 'GOOD') {
maliciousOracle = settlementOracle;
} else if (tradingTerminationOracle?.provider.oracle.status !== 'GOOD') {
maliciousOracle = tradingTerminationOracle;
} }
const { provider } = oracle; if (!settlementOracle && !tradingTerminationOracle) {
return (
<NotificationBanner intent={Intent.Primary}>
<div>{t('There is no oracle for this market yet.')} </div>
</NotificationBanner>
);
}
if (!maliciousOracle) return null;
const { provider } = maliciousOracle;
return ( return (
<> <>
<OracleDialog open={open} onChange={onChange} {...oracle} /> <OracleDialog open={open} onChange={onChange} {...maliciousOracle} />
<NotificationBanner intent={Intent.Danger}> <NotificationBanner intent={Intent.Danger}>
<div> <div>
Oracle status for this market is{' '} Oracle status for this market is{' '}

View File

@ -2,6 +2,7 @@ import { t } from '@vegaprotocol/i18n';
import type { Provider } from '../../oracle-schema'; import type { Provider } from '../../oracle-schema';
import { MarketState, MarketStateMapping } from '@vegaprotocol/types'; import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
import { import {
ButtonLink,
ExternalLink, ExternalLink,
Icon, Icon,
Intent, Intent,
@ -15,6 +16,7 @@ import { getLinkIcon, getVerifiedStatusIcon } from '../oracle-basic-profile';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import type { OracleMarketSpecFieldsFragment } from '../../__generated__/OracleMarketsSpec'; import type { OracleMarketSpecFieldsFragment } from '../../__generated__/OracleMarketsSpec';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import { useState } from 'react';
export const OracleProfileTitle = ({ provider }: { provider: Provider }) => { export const OracleProfileTitle = ({ provider }: { provider: Provider }) => {
const { icon, intent } = getVerifiedStatusIcon(provider); const { icon, intent } = getVerifiedStatusIcon(provider);
@ -78,6 +80,7 @@ export const OracleFullProfile = ({
}) => { }) => {
const { message } = getVerifiedStatusIcon(provider); const { message } = getVerifiedStatusIcon(provider);
const { VEGA_EXPLORER_URL } = useEnvironment(); const { VEGA_EXPLORER_URL } = useEnvironment();
const [showMore, setShowMore] = useState(false);
const links = provider.proofs const links = provider.proofs
.filter((proof) => proof.format === 'url' && proof.available === true) .filter((proof) => proof.format === 'url' && proof.available === true)
@ -86,6 +89,9 @@ export const OracleFullProfile = ({
url: 'url' in proof ? proof.url : '', url: 'url' in proof ? proof.url : '',
icon: getLinkIcon(proof.type), icon: getLinkIcon(proof.type),
})); }));
const signedMessageProofs = provider.proofs.filter(
(proof) => proof.format === 'signed_message' && proof.available === true
);
return ( return (
<div className="flex flex-col text-sm" data-testid="oracle-full-profile"> <div className="flex flex-col text-sm" data-testid="oracle-full-profile">
@ -103,8 +109,15 @@ export const OracleFullProfile = ({
disallowedElements={['img']} disallowedElements={['img']}
linkTarget="_blank" linkTarget="_blank"
> >
{provider.description_markdown} {showMore
? provider.description_markdown
: provider.description_markdown.slice(0, 100) + '...'}
</ReactMarkdown> </ReactMarkdown>
<span>
<ButtonLink onClick={() => setShowMore(!showMore)}>
{!showMore ? t('Read more') : t('Show less')}
</ButtonLink>
</span>
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-6"> <div className="grid grid-cols-2 gap-6">
@ -113,9 +126,12 @@ export const OracleFullProfile = ({
className="dark:text-vega-light-300 text-vega-dark-300 uppercase" className="dark:text-vega-light-300 text-vega-dark-300 uppercase"
data-testid="verified-accounts" data-testid="verified-accounts"
> >
{t('%s proofs of ownership', links.length.toString())} {t('%s %s of ownership', [
provider.proofs.length.toString(),
provider.proofs.length === 1 ? 'proof' : 'proofs',
])}
</p> </p>
{links.length > 0 ? ( {provider.proofs.length > 0 ? (
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{links.map((link) => ( {links.map((link) => (
<ExternalLink <ExternalLink
@ -132,6 +148,31 @@ export const OracleFullProfile = ({
</span> </span>
</ExternalLink> </ExternalLink>
))} ))}
{signedMessageProofs.length > 0 && (
<ExternalLink
key={'more-proofs'}
href={provider.github_link}
className="flex align-items-bottom underline text-sm pt-2"
>
{links.length > 0 ? (
<span className="underline">
{t('And %s more %s', [
signedMessageProofs.length.toString(),
signedMessageProofs.length === 1 ? 'proof' : 'proofs',
])}{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span>
) : (
<span className="underline">
{t('Verify %s %s of ownership', [
signedMessageProofs.length.toString(),
signedMessageProofs.length === 1 ? 'proof' : 'proofs',
])}{' '}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span>
)}
</ExternalLink>
)}
</div> </div>
) : ( ) : (
<p className="dark:text-vega-light-300 text-vega-dark-300"> <p className="dark:text-vega-light-300 text-vega-dark-300">

View File

@ -4,7 +4,12 @@ import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketInfoProvider } from '../components/market-info/market-info-data-provider'; import { marketInfoProvider } from '../components/market-info/market-info-data-provider';
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useMarketOracle = (marketId: string) => { export const useMarketOracle = (
marketId: string,
dataSourceSpecKey:
| 'dataSourceSpecForSettlementData'
| 'dataSourceSpecForTradingTermination' = 'dataSourceSpecForSettlementData'
) => {
const { ORACLE_PROOFS_URL } = useEnvironment(); const { ORACLE_PROOFS_URL } = useEnvironment();
const { data: marketInfo } = useDataProvider({ const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider, dataProvider: marketInfoProvider,
@ -16,8 +21,7 @@ export const useMarketOracle = (marketId: string) => {
return undefined; return undefined;
} }
const dataSourceSpec = const dataSourceSpec =
marketInfo.tradableInstrument.instrument.product marketInfo.tradableInstrument.instrument.product[dataSourceSpecKey];
.dataSourceSpecForSettlementData;
const { data: dataSource, id: dataSourceSpecId } = dataSourceSpec; const { data: dataSource, id: dataSourceSpecId } = dataSourceSpec;
const provider = data.find((provider) => const provider = data.find((provider) =>
provider.proofs.some((proof) => { provider.proofs.some((proof) => {
@ -48,5 +52,5 @@ export const useMarketOracle = (marketId: string) => {
return { provider, dataSourceSpecId }; return { provider, dataSourceSpecId };
} }
return undefined; return undefined;
}, [data, marketInfo]); }, [data, dataSourceSpecKey, marketInfo]);
}; };