Compare commits

..
Author SHA1 Message Date
Matthew Russell a14ed6a997 chore: add revised first bullet 2023-05-19 13:55:30 -07:00
Matthew Russell aa1a875095 chore: remove first discalimer bullet point 2023-05-19 13:32:52 -07:00
Matthew Russell 61b10e0580 chore: align risk message design and welcome dialog 2023-05-19 12:46:26 -07:00
Matthew Russell 0ecd0828cf chore: adjust design of disclaimer dialog 2023-05-19 12:32:01 -07:00
Matthew Russell 1678e17ac3 chore: update layout of disclaimer page to match other pages on governance 2023-05-19 12:05:37 -07:00
maciek 6f3c1cbef7 feat: improve flow of risk disclaimer presentation - remove unnecessary store values 2023-05-19 17:29:20 +02:00
maciek 8da85a2126 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-19 17:25:51 +02:00
maciek d051eb5612 feat: improve flow of risk disclaimer presentation - adjust mainnet telemetry for 2023-05-19 17:25:24 +02:00
Sam Keen 5bd18cc127 feat(governance): avoid showing untitled proposals (#3851) 2023-05-19 16:19:28 +01:00
Art ff3519279d fix(governance): penalties calculation (#3850) 2023-05-19 16:19:07 +01:00
m.ray fd338c7400 fix(orders): cancel all and pegged orders amendable (#3843) 2023-05-19 14:55:08 +00:00
Sam Keen 0db5d0ce87 feat(governance): hide proposal details on market proposals (#3845) 2023-05-19 13:47:48 +00:00
maciek ba73f7f7a6 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-19 15:33:27 +02:00
maciek 775b67b478 feat: improve flow of risk disclaimer presentation - add unit test 2023-05-19 15:33:07 +02:00
Matthew Russell 42c316c8e1 feat(environment): avoid logging node check queries (#3830) 2023-05-19 14:32:27 +01:00
Sam Keen 9e2474d39a feat(governance): proposals-sort-order (#3848) 2023-05-19 14:30:08 +01:00
Joe Tsang 2533e5ec44 test(governance): 3790 proposal tests refactor (#3838) 2023-05-19 14:29:20 +01:00
maciek 99267dd099 feat: improve flow of risk disclaimer presentation - add unit test 2023-05-19 15:14:30 +02:00
maciek 5c436b3bf3 feat: improve flow of risk disclaimer presentation - fix failing unit tests 2023-05-19 14:57:41 +02:00
maciek 904fee83b5 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-19 14:36:29 +02:00
maciek 96078c6cc8 feat: improve flow of risk disclaimer presentation - fix lint problems 2023-05-19 14:36:08 +02:00
dexturr 22a43249ea chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-19 12:08:42 +00:00
m.ray b40ee0caf5 fix(markets): remove queries for internal data sources (#3829) 2023-05-19 12:53:00 +01:00
Edd 95aca70434 fix(explorer): prevent render of signature component on some proposals (#3840) 2023-05-19 12:09:48 +01:00
Sam Keen 5e13266250 feat(governance): change url of upgrade proposals (#3841) 2023-05-19 12:09:40 +01:00
maciek 275d91e7a5 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-19 13:04:12 +02:00
maciek 11bf1b7e48 feat: improve flow of risk disclaimer presentation - refactor for make it more compact 2023-05-19 13:03:44 +02:00
Edd 5c0588887c fix(explorer): fix footer links (#3825) 2023-05-19 10:37:59 +01:00
daro-maj 089a7daac7 test(trading): e2e test for select network and nodes - 0006-NETW (#3828) 2023-05-19 10:37:44 +01:00
Mikołaj Młodzikowski 02b6251b1a feat(ci): support mainnet branching strategy (#3832) 2023-05-19 10:38:36 +02:00
Bartłomiej Głownia 9ce8907861 feat(trading): do not show oracle banner when data is loading (#3821) 2023-05-19 10:01:22 +02:00
maciek 13ded71724 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-19 08:59:07 +02:00
maciek 46435ef743 feat: improve flow of risk disclaimer presentation - make draft for parametrization vega wallet 2023-05-19 08:58:54 +02:00
dexturr 3be9126906 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-19 06:10:00 +00:00
dexturr 77316092d1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-19 00:15:57 +00:00
Dexter Edwards fca229e62a feat(governance): use rest to expose proposal data (#3827) 2023-05-18 22:43:32 +01:00
dexturr 162b167860 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 18:07:36 +00:00
maciek 16d1e127ee Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-18 18:29:35 +02:00
maciek 2423cfb4cc feat: improve flow of risk disclaimer presentation - make draft for parametrization vega wallet 2023-05-18 18:28:47 +02:00
maciek 5b8754365e Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-18 12:02:05 +02:00
maciek d35fb7a26d feat: improve flow of risk disclaimer presentation - make int test more durable 2023-05-17 13:17:23 +02:00
maciek 33be090dd6 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-17 12:11:54 +02:00
maciek b5df733a4e chore: refactor createDocsUrl for fully static - add an int test 2023-05-17 12:11:37 +02:00
maciek 91e58c391f Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-17 11:10:39 +02:00
maciek f1422667dd chore: refactor createDocsUrl for fully static - fix failing int tests 2023-05-17 11:10:14 +02:00
maciek e7851186a7 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-17 08:53:36 +02:00
maciek 3fc4a71edd Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-16 17:09:32 +02:00
maciek e8f0bc7380 feat: improve flow of risk disclaimer presentation - fix typo 2023-05-16 17:09:19 +02:00
maciek c08dca33f4 feat: improve flow of risk disclaimer presentation - fix failling unit tests 2023-05-15 18:00:24 +02:00
maciek 71852faa36 Merge remote-tracking branch 'origin/develop' into feat/3737-console-in-context-messaging 2023-05-15 17:52:29 +02:00
maciek 51a8d99150 feat: improve flow of risk disclaimer presentation - prevent eager connect when np risk accepted 2023-05-15 17:52:02 +02:00
maciek 264b9a59dd feat: improve flow of risk disclaimer presentation 2023-05-15 16:44:21 +02:00
121 changed files with 2885 additions and 1148 deletions
+6 -4
View File
@@ -62,17 +62,19 @@ jobs:
- name: Define variables
run: |
envName=''
domain="vega.rocks"
if [[ "${{ github.event_name }}" = "push" ]]; then
domain="vega.rocks"
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
if [[ "${{ github.ref }}" =~ .*mainnet.* ]]; then
domain="vega.community"
fi
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1"
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ ${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }} = "true" ]]; then
envName="mainnet"
elif [[ "${{ github.ref }}" =~ .*main$ ]]; then
envName="mainnet"
fi
if [[ "${envName}" = "mainnet" ]]; then
domain="vega.xyz"
fi
bucketName="${{ matrix.app }}.${envName}.${domain}"
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
+1
View File
@@ -14,6 +14,7 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
NX_VEGA_GOVERNANCE_URL=https://stagnet1.governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
# App flags
NX_EXPLORER_ASSETS=1
@@ -48,11 +48,13 @@ export const Footer = () => {
</Link>
</div>
<div className="flex pl-2 content-center">
<ExternalLink href={ENV.addresses.feedback}>
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
</ExternalLink>
</div>
{ENV.addresses.feedback ? (
<div className="flex pl-2 content-center">
<ExternalLink href={ENV.addresses.feedback}>
{showFullFeedbackLabel ? t('Share your feedback') : t('Feedback')}
</ExternalLink>
</div>
) : null}
</div>
</footer>
);
@@ -62,9 +64,5 @@ const NodeUrl = ({ url }: { url: string }) => {
// get base url from api url, api sub domain
const urlObj = new URL(url);
const nodeUrl = urlObj.origin.replace(/^[^.]+\./g, '');
return (
<Link href={'https://' + nodeUrl} target="_blank">
{nodeUrl}
</Link>
);
return <span className="cursor-default">{nodeUrl}</span>;
};
@@ -20,12 +20,10 @@ import isEqual from 'lodash/isEqual';
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
if (!market) return null;
const settlementData =
market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData
.data;
const terminationData =
market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data;
const settlementData = market.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const terminationData = market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
@@ -0,0 +1,56 @@
import type { Proposal } from './tx-proposal';
import { proposalRequiresSignatureBundle } from './tx-proposal';
describe('proposalRequiresSignatureBundle', () => {
it('should return false for freeform proposals, which do not require a signature bundle to enact', () => {
const mock = {
terms: {
newFreeform: {},
},
};
expect(proposalRequiresSignatureBundle(mock)).toEqual(false);
});
it('should return false for newMarket proposals, which do not require a signature bundle to enact', () => {
const mock = {
terms: {
newMarket: {},
},
};
expect(proposalRequiresSignatureBundle(mock)).toEqual(false);
});
it('should return true for newAsset proposals, which do require a signature bundle to enact', () => {
const mock = {
terms: {
newAsset: {},
},
};
expect(proposalRequiresSignatureBundle(mock)).toEqual(true);
});
it('should return true for updateAsset proposals, which do require a signature bundle to enact', () => {
const mock = {
terms: {
updateAsset: {},
},
};
expect(proposalRequiresSignatureBundle(mock)).toEqual(true);
});
it('should return false when bad data is supplied', () => {
expect(
proposalRequiresSignatureBundle(false as unknown as Proposal)
).toEqual(false);
expect(
proposalRequiresSignatureBundle(undefined as unknown as Proposal)
).toEqual(false);
expect(
proposalRequiresSignatureBundle({ test: false } as unknown as Proposal)
).toEqual(false);
});
});
@@ -28,11 +28,16 @@ interface TxProposalProps {
* @returns boolean True if a signature bundle is required. Used to fetch a signature bundle
*/
export function proposalRequiresSignatureBundle(proposal?: Proposal): boolean {
const proposalsThatRequireBundles = ['newAsset', 'updateAsset'];
if (!proposal?.terms) {
return false;
}
return !!['newAsset', 'updateAsset'].filter((requiredIfExists) =>
has(proposal.terms, requiredIfExists)
return (
proposalsThatRequireBundles.filter((requiredIfExists) =>
has(proposal.terms, requiredIfExists)
).length > 0
);
}
@@ -81,6 +86,7 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
const tx = proposal.terms?.newAsset || proposal.terms?.updateAsset;
// This component is not rendered if no bundle is required
const SignatureBundleComponent = proposal.terms?.newAsset
? ProposalSignatureBundleNewAsset
: ProposalSignatureBundleUpdateAsset;
+1
View File
@@ -17,6 +17,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
#Test configuration variables
CYPRESS_FAIRGROUND=false
@@ -0,0 +1,113 @@
{
"rationale": {
"title": "New Market Proposal E2E submission",
"description": "E2E new market proposal"
},
"terms": {
"newMarket": {
"changes": {
"decimalPlaces": "5",
"positionDecimalPlaces": "5",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0",
"lpPriceRange": "10",
"instrument": {
"name": "Token test market",
"code": "TEST.24h",
"future": {
"settlementAsset": "73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0",
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": "0"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForTradingTermination": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_EQUALS",
"value": "true"
}
]
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "prices.ETH.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
"metadata": ["sector:energy", "sector:tech", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"liquidityMonitoringParameters": {
"targetStakeParameters": {
"timeWindow": "3600",
"scalingFactor": 10
},
"triggeringRatio": "0.7",
"auctionExtension": "1"
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.01,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.5
}
}
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -7,17 +7,12 @@ import {
import {
createRawProposal,
createTenDigitUnixTimeStampForSpecifiedDays,
enterUniqueFreeFormProposalBody,
generateFreeFormProposalTitle,
getDateFormatForSpecifiedDays,
getProposalIdFromList,
getProposalFromTitle,
getProposalInformationFromTable,
getSubmittedProposalFromProposalList,
goToMakeNewProposal,
governanceProposalType,
submitUniqueRawProposal,
voteForProposal,
waitForProposalSubmitted,
waitForProposalSync,
} from '../../../../governance-e2e/src/support/governance.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governance-e2e/src/support/staking.functions';
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
@@ -38,6 +33,8 @@ const proposalDetailsTitle = '[data-testid="proposal-title"]';
const proposalDetailsDescription = '[data-testid="proposal-description"]';
const openProposals = '[data-testid="open-proposals"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const voteBreakdownToggle = 'vote-breakdown-toggle';
const proposalTermsToggle = 'proposal-terms-toggle';
describe(
'Governance flow for proposal details',
@@ -62,23 +59,25 @@ describe(
// 3001-VOTE-050 3001-VOTE-054 3001-VOTE-055 3002-PROP-019
it('Newly created raw proposal details - shows proposal title and full description', function () {
const proposalDescription =
'I propose that everyone evaluate the following IPFS document and vote Yes if they agree. bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si';
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalIdFromList(rawProposal.rationale.title);
cy.get('@proposalIdText').then((proposalId) => {
cy.get(openProposals).within(() => {
cy.get(`#${proposalId}`).within(() => {
cy.get(viewProposalButton).should('be.visible').click();
});
cy.get(openProposals).within(() => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.get(viewProposalButton).should('be.visible').click();
});
});
cy.get(proposalDetailsTitle)
.should('contain', rawProposal.rationale.title)
.and('be.visible');
cy.get(proposalDetailsTitle).should(
'contain.text',
rawProposal.rationale.title
);
cy.get(proposalDetailsDescription)
.should('contain', rawProposal.rationale.description)
.and('be.visible');
.find('p')
.should('have.text', proposalDescription);
});
cy.getByTestId(proposalTermsToggle).click();
// 3001-VOTE-052
cy.get('code.language-json')
.should('exist')
@@ -89,33 +88,36 @@ describe(
// 3001-VOTE-043
it('Newly created freeform proposal details - shows proposed and closing dates', function () {
const closingVoteHrs = '72';
const proposalTitle = generateFreeFormProposalTitle();
const proposalTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
// const currentDate = new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000)
// const proposedDate = new Date(currentDate.getTime() + 60000)
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody(closingVoteHrs, proposalTitle);
waitForProposalSubmitted();
waitForProposalSync();
submitUniqueRawProposal({
proposalTitle: proposalTitle,
closingTimestamp: proposalTimeStamp,
});
navigateTo(navigation.proposals);
getSubmittedProposalFromProposalList(proposalTitle).within(() =>
getProposalFromTitle(proposalTitle).within(() =>
cy.get(viewProposalButton).click()
);
cy.wrap(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000))
).then((closingDate) => {
getProposalInformationFromTable('Closes on')
.contains(closingDate)
.should('be.visible');
getProposalInformationFromTable('Closes on').should(
'have.text',
closingDate
);
});
cy.wrap(
formatDateWithLocalTimezone(
new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000)
)
).then((proposalDate) => {
getProposalInformationFromTable('Proposed on')
.contains(proposalDate)
.should('be.visible');
getProposalInformationFromTable('Proposed on').should(
'have.text',
proposalDate
);
});
});
@@ -125,13 +127,14 @@ describe(
// 3001-VOTE-067
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
rawProposal.rationale.title
).within(() => cy.get(viewProposalButton).click());
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click()
);
});
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
'be.visible'
);
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Expected to pass')
.contains('👎')
.should('be.visible');
@@ -151,9 +154,9 @@ describe(
it('Newly created proposal details - ability to vote for and against proposal - with minimum required tokens associated', function () {
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
rawProposal.rationale.title
).within(() => cy.get(viewProposalButton).click());
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click()
);
});
// 3001-VOTE-080
cy.getByTestId('vote-buttons').contains('against').should('be.visible');
@@ -179,6 +182,7 @@ describe(
cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Tokens for proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
@@ -220,14 +224,15 @@ describe(
vegaWalletSetSpecifiedApprovalAmount('1000');
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
rawProposal.rationale.title
).within(() => cy.get(viewProposalButton).click());
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click()
);
});
voteForProposal('for');
// 3001-VOTE-079
cy.contains('You voted: For').should('be.visible');
cy.get(proposalVoteProgressForTokens).contains('1').and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total Supply')
.invoke('text')
.then((totalSupply) => {
@@ -235,14 +240,15 @@ describe(
(Number(totalSupply.replace(/,/g, '')) * 0.001) /
100
).toFixed(2);
ethereumWalletConnect();
ensureSpecifiedUnstakedTokensAreAssociated(
tokensRequiredToAchieveResult
);
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
rawProposal.rationale.title
).within(() => cy.get(viewProposalButton).click());
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click()
);
});
cy.get(proposalVoteProgressForPercentage)
.contains('100.00%')
@@ -259,6 +265,7 @@ describe(
cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total tokens voted percentage')
.should('have.text', '0.00%')
.and('be.visible');
@@ -58,14 +58,12 @@ context(
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.get(proposalStatus).should('have.text', 'Enacted ');
cy.get(proposalStatus).should('have.text', 'Enacted');
cy.get(viewProposalButton).click();
});
});
cy.getByTestId('proposal-type').should('have.text', 'New market');
getProposalInformationFromTable('State')
.contains('Enacted')
.and('be.visible');
cy.get(proposalStatus).should('have.text', 'Enacted');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
@@ -87,16 +85,10 @@ context(
.last()
.within(() => cy.get(viewProposalButton).click());
});
getProposalInformationFromTable('State')
.contains('Open')
.and('be.visible');
cy.get(proposalStatus).should('have.text', 'Open');
voteForProposal('for');
getProposalInformationFromTable('State') // 3001-VOTE-047
.contains('Passed', proposalTimeout)
.and('be.visible');
getProposalInformationFromTable('State')
.contains('Enacted', proposalTimeout)
.and('be.visible');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Passed');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
@@ -121,13 +113,9 @@ context(
.last()
.within(() => cy.get(viewProposalButton).click());
});
getProposalInformationFromTable('State')
.contains('Open')
.and('be.visible');
cy.get(proposalStatus).should('have.text', 'Open');
voteForProposal('for');
getProposalInformationFromTable('State')
.contains('Enacted', proposalTimeout)
.and('be.visible');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
});
// 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050
@@ -144,12 +132,8 @@ context(
.last()
.within(() => cy.get(viewProposalButton).click());
});
getProposalInformationFromTable('State')
.contains('Open')
.and('be.visible');
getProposalInformationFromTable('State') // 3001-VOTE-047
.contains('Declined', proposalTimeout)
.and('be.visible');
cy.get(proposalStatus).should('have.text', 'Open');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Declined');
getProposalInformationFromTable('Rejection reason')
.contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED')
.and('be.visible');
@@ -5,10 +5,11 @@ import {
enterRawProposalBody,
enterUniqueFreeFormProposalBody,
generateFreeFormProposalTitle,
getProposalFromTitle,
getProposalInformationFromTable,
getSubmittedProposalFromProposalList,
goToMakeNewProposal,
governanceProposalType,
submitUniqueRawProposal,
voteForProposal,
waitForProposalSubmitted,
waitForProposalSync,
@@ -81,7 +82,6 @@ context(
});
vegaWalletSetSpecifiedApprovalAmount('1000');
cy.associateTokensToVegaWallet('1');
});
beforeEach('visit governance tab', function () {
@@ -95,7 +95,8 @@ context(
navigateTo(navigation.proposals);
});
it('Should be able to see that no proposals exist', function () {
// Test can only pass if run before other proposal tests.
it.skip('Should be able to see that no proposals exist', function () {
// 3001-VOTE-003
cy.get(noOpenProposals)
.should('be.visible')
@@ -107,7 +108,7 @@ context(
// 3002-PROP-002
// 3002-PROP-003
it('Submit a proposal form - shows how many vega tokens are required to make a proposal', function () {
it('Proposal form - shows how many vega tokens are required to make a proposal', function () {
// 3002-PROP-005
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.contains(
@@ -115,8 +116,9 @@ context(
).should('be.visible');
});
// Skipping as currently unable to propose using forms other than raw
// 3002-PROP-011
it('Able to submit a valid freeform proposal - with minimum required tokens associated', function () {
it.skip('Able to submit a valid freeform proposal - with minimum required tokens associated', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM);
cy.get(minVoteButton).should('be.visible'); // 3002-PROP-008
cy.get(maxVoteButton).should('be.visible');
@@ -140,16 +142,13 @@ context(
closeStakingDialog();
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
navigateTo(navigation.proposals);
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody('50', generateFreeFormProposalTitle());
waitForProposalSubmitted();
createRawProposal();
});
it('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
it.skip('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody('0.1', generateFreeFormProposalTitle());
cy.contains('Awaiting network confirmation', epochTimeout).should(
'not.exist'
);
@@ -158,7 +157,7 @@ context(
.should('equal', 'Value must be greater than or equal to 1.');
});
it('Creating a proposal - proposal rejected - when closing time later than system default', function () {
it.skip('Creating a proposal - proposal rejected - when closing time later than system default', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody(
'100000',
@@ -185,17 +184,13 @@ context(
navigateTo(navigation.proposals);
cy.get(rejectProposalsLink).click();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
rawProposal.rationale.title
).within(() => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.contains('Rejected').should('be.visible');
cy.contains('Close time too late').should('be.visible');
cy.get(viewProposalButton).click();
});
});
getProposalInformationFromTable('State')
.contains('Rejected')
.and('be.visible');
cy.getByTestId('proposal-status').should('have.text', 'Rejected');
getProposalInformationFromTable('Rejection reason')
.contains('PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE')
.and('be.visible');
@@ -290,18 +285,19 @@ context(
const proposalTitle = generateFreeFormProposalTitle();
ensureSpecifiedUnstakedTokensAreAssociated('1');
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody('50', proposalTitle);
waitForProposalSubmitted();
submitUniqueRawProposal({ proposalTitle: proposalTitle });
ethereumWalletConnect();
stakingPageDisassociateTokens('0.0001');
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
'0.9999'
);
});
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
'0.9999'
);
});
navigateTo(navigation.proposals);
getSubmittedProposalFromProposalList(proposalTitle).within(() =>
getProposalFromTitle(proposalTitle).within(() =>
cy.get(viewProposalButton).click()
);
cy.contains('Vote breakdown').should('be.visible', {
@@ -319,9 +315,9 @@ context(
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="disconnect"]').click();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(
rawProposal.rationale.title
).within(() => cy.get(viewProposalButton).click());
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click()
);
});
// 3001-VOTE-075
// 3001-VOTE-076
@@ -8,6 +8,7 @@ import {
import {
getProposalInformationFromTable,
goToMakeNewProposal,
governanceProposalType,
voteForProposal,
waitForProposalSubmitted,
} from '../../support/governance.functions';
@@ -56,18 +57,8 @@ const fUSDCId =
const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 };
const governanceProposalType = {
NETWORK_PARAMETER: 'Network parameter',
NEW_MARKET: 'New market',
UPDATE_MARKET: 'Update market',
NEW_ASSET: 'New asset',
UPDATE_ASSET: 'Update asset',
FREEFORM: 'Freeform',
RAW: 'raw proposal',
};
// 3001-VOTE-007
context(
context.skip(
'Governance flow - form validations for different governance proposals',
{ tags: '@slow' },
function () {
@@ -6,16 +6,15 @@ import {
waitForSpinner,
} from '../../support/common.functions';
import {
createFreeformProposal,
createRawProposal,
createTenDigitUnixTimeStampForSpecifiedDays,
enterRawProposalBody,
generateFreeFormProposalTitle,
getProposalIdFromList,
getProposalFromTitle,
getProposalInformationFromTable,
getSubmittedProposalFromProposalList,
goToMakeNewProposal,
governanceProposalType,
submitUniqueRawProposal,
voteForProposal,
waitForProposalSubmitted,
waitForProposalSync,
@@ -24,10 +23,14 @@ import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/stakin
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
const proposalListItem = 'proposals-list-item';
const openProposals = '[data-testid="open-proposals"]';
const voteStatus = '[data-testid="vote-status"]';
const voteStatus = 'vote-status';
const proposalType = 'proposal-type';
const proposalStatus = 'proposal-status';
const proposalClosingDate = '[data-testid="vote-details"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const voteBreakDownToggle = 'vote-breakdown-toggle';
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
before('connect wallets and set approval limit', function () {
@@ -60,12 +63,12 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
navigateTo(navigation.proposals);
cy.get(openProposals).within(() => {
cy.get(proposalClosingDate).first().should('contain.text', 'year');
cy.get(proposalClosingDate).should('contain.text', 'months');
cy.get(proposalClosingDate)
.last()
.first()
.invoke('text')
.should('match', /days|minutes/);
cy.get(proposalClosingDate).should('contain.text', 'months');
cy.get(proposalClosingDate).last().should('contain.text', 'year');
});
});
@@ -73,36 +76,27 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
const proposerId = Cypress.env('vegaWalletPublicKey');
const proposalTitle = generateFreeFormProposalTitle();
createFreeformProposal(proposalTitle);
getProposalIdFromList(proposalTitle);
cy.get('@proposalIdText').then((proposalId) => {
cy.get('[data-testid="set-proposals-filter-visible"]').click();
cy.get('[data-testid="filter-input"]').type(proposerId);
cy.get(`#${proposalId}`).should('contain', proposalId);
});
submitUniqueRawProposal({ proposalTitle: proposalTitle });
cy.get('[data-testid="set-proposals-filter-visible"]').click();
cy.get('[data-testid="filter-input"]').type(proposerId);
// cy.get(`#${proposalId}`).should('contain', proposalId);
cy.contains(proposalTitle).should('be.visible');
cy.get('[data-testid="filter-input"]').type('123');
cy.getByTestId(proposalListItem).should('not.exist');
});
it('Newly created proposals list - shows title and portion of summary', function () {
createRawProposal(this.minProposerBalance); // 3001-VOTE-052
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalIdFromList(rawProposal.rationale.title);
cy.get('@proposalIdText').then((proposalId) => {
cy.get(openProposals).within(() => {
// 3001-VOTE-008
// 3001-VOTE-034
cy.get(`#${proposalId}`)
// 3001-VOTE-097
.should('contain', rawProposal.rationale.title)
.and('be.visible');
cy.get(`#${proposalId}`)
.should(
'contain',
rawProposal.rationale.description.substring(0, 59)
)
.and('be.visible');
});
});
});
const proposalPath = '/proposals/new-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: enactmentTimestamp,
}); // 3001-VOTE-052
// 3001-VOTE-008
// 3001-VOTE-034
// 3001-VOTE-097
cy.contains('New Market Proposal E2E submission');
cy.contains('Code: TEST.24h. fBTC settled future.').should('be.visible');
});
it('Newly created proposals list - shows open proposals in an open state', function () {
@@ -110,23 +104,11 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
// 3001-VOTE-035
createRawProposal(this.minProposerBalance);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getSubmittedProposalFromProposalList(rawProposal.rationale.title).within(
() => {
cy.get(viewProposalButton).should('be.visible').click();
}
);
cy.get('@proposalIdText').then((proposalId) => {
getProposalInformationFromTable('ID')
.contains(String(proposalId))
.and('be.visible');
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.get(viewProposalButton).should('be.visible');
cy.getByTestId(proposalType).should('have.text', 'Freeform');
cy.getByTestId(proposalStatus).should('have.text', 'Open');
});
getProposalInformationFromTable('State')
.contains('Open')
.and('be.visible');
getProposalInformationFromTable('Type')
.contains('Freeform')
.and('be.visible');
});
});
@@ -134,18 +116,22 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
it('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
const proposalTitle = generateFreeFormProposalTitle();
createFreeformProposal(proposalTitle);
getSubmittedProposalFromProposalList(proposalTitle).within(() => {
submitUniqueRawProposal({ proposalTitle: proposalTitle });
getProposalFromTitle(proposalTitle).within(() => {
// 3001-VOTE-039
cy.get(voteStatus).should('have.text', 'Participation not reached');
cy.getByTestId(voteStatus).should(
'have.text',
'Participation not reached'
);
cy.get(viewProposalButton).click();
});
voteForProposal('for');
navigateTo(navigation.proposals);
getSubmittedProposalFromProposalList(proposalTitle).within(() => {
cy.get(voteStatus).should('have.text', 'Set to pass');
getProposalFromTitle(proposalTitle).within(() => {
cy.getByTestId(voteStatus).should('have.text', 'Set to pass');
cy.get(viewProposalButton).click();
});
cy.getByTestId(voteBreakDownToggle).click();
getProposalInformationFromTable('Token participation met')
.contains('👍')
.should('be.visible');
@@ -96,7 +96,7 @@ context(
navigateTo(navigation.validators);
// 2002-SINC-007
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
});
it('Able to view validators staked by me', function () {
@@ -146,7 +146,7 @@ context(
verifyThisEpochValue(2.0);
closeStakingDialog();
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
});
it('Able to stake against a validator - using vega from both wallet and vesting contract', function () {
@@ -166,10 +166,11 @@ context(
verifyThisEpochValue(6.0);
closeStakingDialog();
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '6.00', '100.00%');
validateValidatorListTotalStakeAndShare('0', '3,006.00', '50.05%');
});
it('Able to stake against multiple validators', function () {
vegaWalletTeardown();
stakingPageAssociateTokens('5');
verifyUnstakedBalance(5.0);
cy.get('button').contains('Select a validator to nominate').click();
@@ -197,14 +198,10 @@ context(
.eq(1)
.within(() => {
cy.getByTestId(stakeValidatorListTotalStake)
.should('have.text', '2.00')
.should('have.text', '3,002.00')
.and('be.visible');
cy.getByTestId(stakeValidatorListTotalShare)
.should('have.text', '66.67%')
.and('be.visible');
cy.getByTestId(stakeValidatorListTotalStake)
.scrollIntoView()
.should('have.text', '2.00')
.should('have.text', '50.01%')
.and('be.visible');
});
cy.get(`[row-id="${1}"]`)
@@ -212,14 +209,10 @@ context(
.within(() => {
cy.getByTestId(stakeValidatorListTotalStake)
.scrollIntoView()
.should('have.text', '1.00')
.should('have.text', '3,001.00')
.and('be.visible');
cy.getByTestId(stakeValidatorListTotalShare)
.should('have.text', '33.33%')
.and('be.visible');
cy.getByTestId(stakeValidatorListTotalStake)
.scrollIntoView()
.should('have.text', '1.00')
.should('have.text', '49.99%')
.and('be.visible');
});
});
@@ -282,7 +275,7 @@ context(
txTimeout
);
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '0.00', '0.00%');
validateValidatorListTotalStakeAndShare('0', '3,000.00', '50.00%');
cy.getByTestId(userStakeBtn).should('not.exist');
cy.getByTestId(userStake).should('not.exist');
@@ -354,7 +347,7 @@ context(
txTimeout
);
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '0.00', '0.00%');
validateValidatorListTotalStakeAndShare('0', '3,000.00', '50.00%');
});
it('Disassociating all vesting contract tokens max - removes all staked tokens', function () {
@@ -382,7 +375,7 @@ context(
txTimeout
);
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '0.00', '0.00%');
validateValidatorListTotalStakeAndShare('0', '3,000.00', '50.00%');
});
it('Disassociating some tokens - prioritizes unstaked tokens', function () {
@@ -404,7 +397,7 @@ context(
});
verifyStakedBalance(2.0);
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
});
it('Associating wallet tokens - when some already staked - auto stakes tokens to staked validator', function () {
@@ -111,10 +111,10 @@ context(
// 1004-ASSO-028
// 1004-ASSO-029
// 1004-ASSO-031
vegaWalletTeardown();
stakingPageAssociateTokens('2');
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('6,002.00');
cy.get('button').contains('Select a validator to nominate').click();
stakingPageDisassociateTokens('2');
cy.getByTestId('currency-title', txTimeout).should(
@@ -125,17 +125,18 @@ context(
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 6);
cy.getByTestId('eth-wallet-associated-balances', txTimeout).should(
'not.exist'
);
verifyEthWalletTotalAssociatedBalance('0.00');
cy.get(
'[data-testid="eth-wallet-associated-balances"]:visible',
txTimeout
).should('have.length', 2);
verifyEthWalletTotalAssociatedBalance('6,000.00');
});
it('Able to associate more tokens than the approved amount of 1000 - requires re-approval', function () {
//1004-ASSO-011
stakingPageAssociateTokens('1001', { approve: true });
verifyEthWalletAssociatedBalance('1,001.00');
verifyEthWalletTotalAssociatedBalance('1,001.00');
verifyEthWalletTotalAssociatedBalance('7,001.00');
cy.get(vegaWallet)
.last()
.within(() => {
@@ -47,11 +47,8 @@ context(
function () {
before('visit withdrawals and connect vega wallet', function () {
cy.visit('/');
// When running tests locally, will fail if run without restarting capsule
cy.updateCapsuleMultiSig().then(() => {
ethereumWalletConnect();
depositAsset(usdcEthAddress, '1000', 5);
});
ethereumWalletConnect();
depositAsset(usdcEthAddress, '1000', 5);
});
beforeEach('Navigate to withdrawal page', function () {
@@ -9,6 +9,7 @@ import {
import {
enterUniqueFreeFormProposalBody,
goToMakeNewProposal,
governanceProposalType,
} from '../../support/governance.functions';
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
@@ -50,7 +51,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
const expectedErrorTxt = `You are connected in a view only state for public key: ${vegaWalletPubKey}. In order to send transactions you must connect to a real wallet.`;
navigateTo(navigation.proposals);
goToMakeNewProposal('Freeform');
goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody('50', 'pub key proposal test');
cy.getByTestId('dialog-content')
.first()
@@ -1,8 +1,12 @@
import { format } from 'date-fns';
import { closeDialog, navigateTo, navigation } from './common.functions';
import {
closeDialog,
navigateTo,
navigation,
waitForSpinner,
} from './common.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions';
const newProposalButton = '[data-testid="new-proposal-link"]';
const proposalInformationTableRows = '[data-testid="key-value-table-row"]';
const proposalListItem = '[data-testid="proposals-list-item"]';
const newProposalTitle = '[data-testid="proposal-title"]';
@@ -46,6 +50,53 @@ export function enterRawProposalBody(timestamp: number) {
});
}
export function submitUniqueRawProposal(proposalFields: {
proposalBody?: string;
proposalTitle?: string;
proposalDescription?: string;
closingTimestamp?: number;
enactmentTimestamp?: number;
submit?: boolean;
}) {
goToMakeNewProposal(governanceProposalType.RAW);
let proposalBodyPath = '/proposals/raw.json';
if (proposalFields.proposalBody) {
proposalBodyPath = proposalFields.proposalBody;
}
cy.fixture(proposalBodyPath).then((rawProposal) => {
if (proposalFields.proposalTitle) {
rawProposal.rationale.title = proposalFields.proposalTitle;
cy.wrap(proposalFields.proposalTitle).as('proposalTitle');
}
if (proposalFields.proposalDescription) {
rawProposal.rationale.description = proposalFields.proposalDescription;
}
if (proposalFields.closingTimestamp) {
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
} else {
const minTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
rawProposal.terms.closingTimestamp = minTimeStamp;
}
if (proposalFields.enactmentTimestamp) {
rawProposal.terms.enactmentTimestamp = proposalFields.enactmentTimestamp;
}
const proposalPayload = JSON.stringify(rawProposal);
cy.get(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
if (proposalFields.submit !== false) {
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.wrap(rawProposal).as('rawProposal');
waitForProposalSubmitted();
waitForProposalSync();
navigateTo(navigation.proposals);
}
});
}
export function enterUniqueFreeFormProposalBody(
timestamp: string,
proposalTitle: string
@@ -58,6 +109,10 @@ export function enterUniqueFreeFormProposalBody(
cy.getByTestId('proposal-submit').should('be.visible').click();
}
export function getProposalFromTitle(proposalTitle: string) {
return cy.contains(proposalTitle).parentsUntil(proposalListItem).last();
}
export function getSubmittedProposalFromProposalList(proposalTitle: string) {
getProposalIdFromList(proposalTitle);
cy.get('@proposalIdText').then((proposalId) => {
@@ -120,13 +175,17 @@ export function waitForProposalSync() {
});
}
export function goToMakeNewProposal(proposalType: string) {
navigateTo(navigation.proposals);
cy.get(newProposalButton).should('be.visible').click();
export function goToMakeNewProposal(proposalType: governanceProposalType) {
cy.visit('/proposals/propose');
waitForSpinner();
cy.url().should('include', '/proposals/propose');
cy.get(navigation.pageSpinner, { timeout: 20000 }).should('not.exist');
cy.get('li').should('contain.text', proposalType).and('be.visible');
cy.get('li').contains(proposalType).click();
if (proposalType == governanceProposalType.RAW) {
cy.get('[href="/proposals/propose/raw"]').click();
} else {
cy.get('li').should('contain.text', proposalType).and('be.visible');
cy.get('li').contains(proposalType).click();
}
}
export function waitForProposalSubmitted() {
@@ -163,11 +222,12 @@ export function createFreeformProposal(proposalTitle: string) {
navigateTo(navigation.proposals);
}
export const governanceProposalType = {
NETWORK_PARAMETER: 'Network parameter',
NEW_MARKET: 'New market',
UPDATE_MARKET: 'Update market',
NEW_ASSET: 'New asset',
FREEFORM: 'Freeform',
RAW: 'raw proposal',
};
export enum governanceProposalType {
NETWORK_PARAMETER = 'Network parameter',
NEW_MARKET = 'New market',
UPDATE_MARKET = 'Update market',
NEW_ASSET = 'New asset',
UPDATE_ASSET = 'Update asset',
FREEFORM = 'Freeform',
RAW = 'raw proposal',
}
@@ -85,6 +85,132 @@ export function createFreeFormProposalTxBody(): ProposalSubmissionBody {
};
}
export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
const MIN_CLOSE_SEC = 5;
const MIN_ENACT_SEC = 7;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
return {
proposalSubmission: {
rationale: {
title: 'New Market Proposal E2E submission',
description: 'E2E new market proposal',
},
terms: {
newMarket: {
changes: {
decimalPlaces: '5',
positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0',
lpPriceRange: '10',
instrument: {
name: 'Token test market',
code: 'TEST.24h',
future: {
settlementAsset:
'73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
quoteName: 'fBTC',
dataSourceSpecForSettlementData: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'prices.ETH.value',
type: 'TYPE_INTEGER' as const,
numberDecimalPlaces: '0',
},
conditions: [
{
operator: 'OPERATOR_GREATER_THAN' as const,
value: '0',
},
],
},
],
},
},
},
dataSourceSpecForTradingTermination: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'trading.terminated.ETH5',
type: 'TYPE_BOOLEAN' as const,
},
conditions: [
{
operator: 'OPERATOR_EQUALS' as const,
value: 'true',
},
],
},
],
},
},
},
dataSourceSpecBinding: {
settlementDataProperty: 'prices.ETH.value',
tradingTerminationProperty: 'trading.terminated.ETH5',
},
},
},
metadata: ['sector:energy', 'sector:tech', 'source:docs.vega.xyz'],
priceMonitoringParameters: {
triggers: [
{
horizon: '43200',
probability: '0.9999999',
auctionExtension: '600',
},
],
},
liquidityMonitoringParameters: {
targetStakeParameters: {
timeWindow: '3600',
scalingFactor: 10,
},
triggeringRatio: '0.7',
auctionExtension: '1',
},
logNormal: {
tau: 0.0001140771161,
riskAversionParameter: 0.01,
params: {
mu: 0,
r: 0.016,
sigma: 0.5,
},
},
},
},
closingTimestamp,
enactmentTimestamp,
},
},
};
}
export function mockNetworkUpgradeProposal() {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Nodes', nodeData);
@@ -67,7 +67,6 @@ export async function faucetAsset(assetEthAddress: string) {
export async function vegaWalletTeardown() {
cy.get(associatedAmountInWallet)
.should('be.visible')
.invoke('text')
.then((associatedAmount) => {
cy.get('body').then(($body) => {
@@ -82,9 +81,11 @@ export async function vegaWalletTeardown() {
cy.get(vegaWalletContainer).within(() => {
cy.get(associatedAmountInWallet, {
timeout: transactionTimeout,
}).contains('0.00', {
timeout: transactionTimeout,
});
})
.should('have.length', 1, { timeout: transactionTimeout })
.contains('0.00', {
timeout: transactionTimeout,
});
});
});
}
+1
View File
@@ -15,6 +15,7 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
#Test configuration variables
CYPRESS_FAIRGROUND=false
+1
View File
@@ -17,6 +17,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
#Test configuration variables
CYPRESS_FAIRGROUND=false
+1
View File
@@ -11,3 +11,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
+1
View File
@@ -12,3 +12,4 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
+1
View File
@@ -8,3 +8,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
+1
View File
@@ -12,3 +12,4 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
+1
View File
@@ -9,3 +9,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
@@ -0,0 +1,40 @@
import { t } from '@vegaprotocol/i18n';
import {
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import Routes from '../../routes/routes';
export const RiskMessage = () => {
return (
<>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
<ul className="list-[square] ml-4">
<li>
{t(
'You may encounter bugs, loss of functionality or loss of assets using the App.'
)}
</li>
<li>
{t(
'No party accepts any liability for any losses whatsoever related to its use.'
)}
</li>
</ul>
</div>
<p className="mb-8">
{t(
'By using the Vega Governance App, you acknowledge that you have read and understood the'
)}{' '}
<ExternalLink href={Routes.DISCLAIMER} className="underline">
<span className="flex items-center gap-1">
<span>{t('Vega Governance Disclaimer')}</span>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</span>
</ExternalLink>
.
</p>
</>
);
};
@@ -4,9 +4,11 @@ import {
useAppState,
} from '../../contexts/app-state/app-state-context';
import { Connectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
export const VegaWalletDialogs = () => {
const { appState, appDispatch } = useAppState();
return (
<>
<VegaConnectDialog
@@ -17,6 +19,7 @@ export const VegaWalletDialogs = () => {
isOpen: open,
})
}
riskMessage={<RiskMessage />}
/>
<VegaManageDialog
+1
View File
@@ -62,6 +62,7 @@ export const ENV = {
ethWalletMnemonic: windowOrDefault('NX_ETH_WALLET_MNEMONIC'),
localProviderUrl: windowOrDefault('NX_LOCAL_PROVIDER_URL'),
delegationsPagination: windowOrDefault('NX_DELEGATIONS_PAGINATION'),
rest: windowOrDefault('NX_VEGA_REST_URL'),
flags: {
NETWORK_DOWN: TRUTHY.includes(windowOrDefault('NX_NETWORK_DOWN')),
MOCK: TRUTHY.includes(windowOrDefault('NX_MOCKED')),
@@ -815,5 +815,11 @@
"NoThanks": "No thanks",
"ShareData": "Share data",
"ContinueSharingData": "Continue sharing data",
"NodeUnsuitable": "Node: {{url}} is unsuitable"
"NodeUnsuitable": "Node: {{url}} is unsuitable",
"Disclaimer": "Disclaimer",
"disclaimer1": "The Vega Governance App allows the Vega network to arrive at on-chain decisions, where tokenholders can create proposals that other tokenholders can vote to approve or reject. Vega supports on-chain proposals for creating markets and assets, and changing network parameters, markets and assets. Vega also supports freeform proposals for community suggestions that will not be enacted on-chain.",
"disclaimer2": "The Vega Governance App is free, public and open source software. Software upgrades may contain bugs or security vulnerabilities that might result in loss of functionality.",
"disclaimer3": "The Vega Governance App uses data obtained from nodes on the Vega Blockchain. The developers of the Vega Governance App do not operate or run the Vega Blockchain or any other blockchain.",
"disclaimer4": "The Vega Governance App is provided “as is”. The developers of the Vega Governance App make no representations or warranties of any kind, whether express or implied, statutory or otherwise regarding the Vega Governance App. They disclaim all warranties of merchantability, quality, fitness for purpose. They disclaim all warranties that the Vega Governance App is free of harmful components or errors.",
"disclaimer5": "No developer of the Vega Governance App accepts any responsibility for, or liability to users in connection with their use of the Vega Governance App."
}
@@ -0,0 +1,21 @@
import type { RouteChildProps } from '..';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { Heading } from '../../components/heading';
import { useTranslation } from 'react-i18next';
const Disclaimer = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
return (
<>
<Heading title={t('Disclaimer')} />
<p className="mb-6 mt-10">{t('disclaimer1')}</p>
<p className="mb-6">{t('disclaimer2')}</p>
<p className="mb-6">{t('disclaimer3')}</p>
<p className="mb-8">{t('disclaimer4')}</p>
<p className="mb-8">{t('disclaimer5')}</p>
</>
);
};
export default Disclaimer;
+15 -6
View File
@@ -27,6 +27,10 @@ import type { ProposalFieldsFragment } from '../proposals/proposals/__generated_
import type { NodesFragmentFragment } from '../staking/home/__generated__/Nodes';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
import {
orderByDate,
orderByUpgradeBlockHeight,
} from '../proposals/components/proposals-list/proposals-list';
const nodesToShow = 6;
@@ -200,17 +204,17 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
const proposals = useMemo(
() =>
proposalsData
? getNotRejectedProposals<ProposalFieldsFragment>(
proposalsData.proposalsConnection
)
? getNotRejectedProposals(proposalsData.proposalsConnection)
: [],
[proposalsData]
);
const sortedProposals = useMemo(() => orderByDate(proposals), [proposals]);
const protocolUpgradeProposals = useMemo(
() =>
protocolUpgradesData
? getNotRejectedProtocolUpgradeProposals<ProtocolUpgradeProposalFieldsFragment>(
? getNotRejectedProtocolUpgradeProposals(
protocolUpgradesData.protocolUpgradeProposals
).filter(
(p) =>
@@ -221,15 +225,20 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
[protocolUpgradesData]
);
const sortedProtocolUpgradeProposals = useMemo(
() => orderByUpgradeBlockHeight(protocolUpgradeProposals),
[protocolUpgradeProposals]
);
const totalProposalsDesired = 4;
const protocolUpgradeProposalsToShow = protocolUpgradeProposals.slice(
const protocolUpgradeProposalsToShow = sortedProtocolUpgradeProposals.slice(
0,
totalProposalsDesired
);
const proposalsToShow =
protocolUpgradeProposalsToShow.length === totalProposalsDesired
? []
: proposals.slice(
: sortedProposals.slice(
0,
totalProposalsDesired - protocolUpgradeProposalsToShow.length
);
@@ -124,7 +124,7 @@ describe('Proposal header', () => {
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
'New asset proposal'
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
@@ -21,6 +21,7 @@ export const ProposalHeader = ({
let details: ReactNode;
let proposalType = '';
let fallbackTitle = '';
const title = proposal?.rationale.title.trim();
@@ -29,6 +30,7 @@ export const ProposalHeader = ({
switch (change?.__typename) {
case 'NewMarket': {
proposalType = 'NewMarket';
fallbackTitle = t('NewMarketProposal');
details = (
<>
<span>
@@ -50,6 +52,7 @@ export const ProposalHeader = ({
}
case 'UpdateMarket': {
proposalType = 'UpdateMarket';
fallbackTitle = t('UpdateMarketProposal');
details = (
<>
<span>{t('Market change')}:</span>{' '}
@@ -60,6 +63,7 @@ export const ProposalHeader = ({
}
case 'NewAsset': {
proposalType = 'NewAsset';
fallbackTitle = t('NewAssetProposal');
details = (
<>
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '}
@@ -81,6 +85,7 @@ export const ProposalHeader = ({
}
case 'UpdateNetworkParameter': {
proposalType = 'NetworkParameter';
fallbackTitle = t('NetworkParameterProposal');
details = (
<>
<span>{t('Change')}:</span>{' '}
@@ -95,11 +100,13 @@ export const ProposalHeader = ({
}
case 'NewFreeform': {
proposalType = 'Freeform';
fallbackTitle = t('FreeformProposal');
details = <span />;
break;
}
case 'UpdateAsset': {
proposalType = 'UpdateAsset';
fallbackTitle = t('UpdateAssetProposal');
details = (
<>
<span>{t('AssetID')}:</span>{' '}
@@ -115,10 +122,14 @@ export const ProposalHeader = ({
<div data-testid="proposal-title">
{isListItem ? (
<header>
<SubHeading title={titleContent || t('Unknown proposal')} />
<SubHeading
title={titleContent || fallbackTitle || t('Unknown proposal')}
/>
</header>
) : (
<Heading title={titleContent || t('Unknown proposal')} />
<Heading
title={titleContent || fallbackTitle || t('Unknown proposal')}
/>
)}
</div>
@@ -43,13 +43,17 @@ jest.mock('../list-asset', () => ({
it('Renders with data-testid', async () => {
const proposal = generateProposal();
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
render(
<Proposal restData={{}} proposal={proposal as ProposalQuery['proposal']} />
);
expect(await screen.findByTestId('proposal')).toBeInTheDocument();
});
it('renders each section', async () => {
const proposal = generateProposal();
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
render(
<Proposal restData={{}} proposal={proposal as ProposalQuery['proposal']} />
);
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
@@ -76,6 +80,8 @@ it('renders whitelist section if proposal is new asset and source is erc20', asy
},
},
});
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
render(
<Proposal restData={{}} proposal={proposal as ProposalQuery['proposal']} />
);
expect(screen.getByTestId('proposal-list-asset')).toBeInTheDocument();
});
@@ -24,9 +24,11 @@ export enum ProposalType {
}
export interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
restData: any;
}
export const Proposal = ({ proposal }: ProposalProps) => {
export const Proposal = ({ proposal, restData }: ProposalProps) => {
const { params, loading, error } = useNetworkParams([
NetworkParams.governance_proposal_market_minVoterBalance,
NetworkParams.governance_proposal_updateMarket_minVoterBalance,
@@ -99,12 +101,15 @@ export const Proposal = ({ proposal }: ProposalProps) => {
<ProposalDescription description={proposal.rationale.description} />
</div>
<div className="mb-4">
<ProposalTerms data={proposal.terms} />
</div>
{proposal.terms.change.__typename !== 'NewMarket' &&
proposal.terms.change.__typename !== 'UpdateMarket' && (
<div className="mb-4">
<ProposalTerms data={proposal.terms} />
</div>
)}
<div className="mb-6">
<ProposalJson proposal={proposal} />
<ProposalJson proposal={restData?.data?.proposal} />
</div>
<div className="mb-10">
@@ -132,10 +132,10 @@ describe('Proposals list', () => {
const closedProposalsItems = closedProposals.getAllByTestId(
'proposals-list-item'
);
expect(openProposalsItems[0]).toHaveAttribute('id', 'proposal1');
expect(openProposalsItems[1]).toHaveAttribute('id', 'proposal2');
expect(closedProposalsItems[0]).toHaveAttribute('id', 'proposal4');
expect(closedProposalsItems[1]).toHaveAttribute('id', 'proposal3');
expect(openProposalsItems[0]).toHaveAttribute('id', 'proposal2');
expect(openProposalsItems[1]).toHaveAttribute('id', 'proposal1');
expect(closedProposalsItems[0]).toHaveAttribute('id', 'proposal3');
expect(closedProposalsItems[1]).toHaveAttribute('id', 'proposal4');
});
it('Displays info on no proposals', () => {
@@ -1,5 +1,6 @@
import orderBy from 'lodash/orderBy';
import { isFuture } from 'date-fns';
import { useState } from 'react';
import { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Heading, SubHeading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
@@ -30,6 +31,25 @@ interface SortedProtocolUpgradeProposalsProps {
closed: ProtocolUpgradeProposalFieldsFragment[];
}
export const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
arr,
[
(p) => new Date(p?.terms?.closingDatetime).getTime(),
(p) => new Date(p?.datetime).getTime(),
],
['asc', 'asc']
);
export const orderByUpgradeBlockHeight = (
arr: ProtocolUpgradeProposalFieldsFragment[]
) =>
orderBy(
arr,
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
['desc', 'desc']
);
export const ProposalsList = ({
proposals,
protocolUpgradeProposals,
@@ -37,35 +57,57 @@ export const ProposalsList = ({
}: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const sortedProposals = proposals.reduce(
(acc: SortedProposalsProps, proposal) => {
if (isFuture(new Date(proposal?.terms.closingDatetime))) {
acc.open.push(proposal);
} else {
acc.closed.push(proposal);
}
return acc;
},
{
open: [],
closed: [],
}
);
const sortedProtocolUpgradeProposals = protocolUpgradeProposals.reduce(
(acc: SortedProtocolUpgradeProposalsProps, proposal) => {
if (Number(proposal?.upgradeBlockHeight) > Number(lastBlockHeight)) {
acc.open.push(proposal);
} else {
acc.closed.push(proposal);
const sortedProposals: SortedProposalsProps = useMemo(() => {
const initialSorting = proposals.reduce(
(acc: SortedProposalsProps, proposal) => {
if (isFuture(new Date(proposal?.terms.closingDatetime))) {
acc.open.push(proposal);
} else {
acc.closed.push(proposal);
}
return acc;
},
{
open: [],
closed: [],
}
return acc;
},
{
open: [],
closed: [],
}
);
);
return {
open:
initialSorting.open.length > 0
? orderByDate(initialSorting.open as ProposalFieldsFragment[])
: [],
closed:
initialSorting.closed.length > 0
? orderByDate(
initialSorting.closed as ProposalFieldsFragment[]
).reverse()
: [],
};
}, [proposals]);
const sortedProtocolUpgradeProposals: SortedProtocolUpgradeProposalsProps =
useMemo(() => {
const initialSorting = protocolUpgradeProposals.reduce(
(acc: SortedProtocolUpgradeProposalsProps, proposal) => {
if (Number(proposal?.upgradeBlockHeight) > Number(lastBlockHeight)) {
acc.open.push(proposal);
} else {
acc.closed.push(proposal);
}
return acc;
},
{
open: [],
closed: [],
}
);
return {
open: orderByUpgradeBlockHeight(initialSorting.open),
closed: orderByUpgradeBlockHeight(initialSorting.closed).reverse(),
};
}, [protocolUpgradeProposals, lastBlockHeight]);
const filterPredicate = (
p: ProposalFieldsFragment | ProposalQuery['proposal']
@@ -107,7 +107,7 @@ export const ProtocolUpgradeProposalsListItem = ({
<div className="grid grid-cols-1 mt-3">
<div className="justify-self-end">
<Link
to={`${Routes.PROPOSALS}/protocol-upgrade/${stripFullStops(
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
proposal.vegaReleaseTag
)}`}
>
@@ -5,9 +5,14 @@ import { useParams } from 'react-router-dom';
import { Proposal } from '../components/proposal';
import { ProposalNotFound } from '../components/proposal-not-found';
import { useProposalQuery } from './__generated__/Proposal';
import { useFetch } from '@vegaprotocol/react-helpers';
import { ENV } from '../../../config';
export const ProposalContainer = () => {
const params = useParams<{ proposalId: string }>();
const {
state: { data: restData },
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
const { data, loading, error, refetch } = useProposalQuery({
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
@@ -23,7 +28,7 @@ export const ProposalContainer = () => {
return (
<AsyncRenderer loading={loading} error={error} data={data}>
{data?.proposal ? (
<Proposal proposal={data.proposal} />
<Proposal proposal={data.proposal} restData={restData} />
) : (
<ProposalNotFound />
)}
@@ -1,3 +1,4 @@
import flow from 'lodash/flow';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -6,36 +7,15 @@ import { SplashLoader } from '../../../components/splash-loader';
import { ProposalsList } from '../components/proposals-list';
import { useProposalsQuery } from './__generated__/Proposals';
import { getNodes } from '@vegaprotocol/utils';
import flow from 'lodash/flow';
import {
ProposalState,
ProtocolUpgradeProposalStatus,
} from '@vegaprotocol/types';
import type { NodeConnection, NodeEdge } from '@vegaprotocol/utils';
import type { ProposalFieldsFragment } from './__generated__/Proposals';
import orderBy from 'lodash/orderBy';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
arr,
[
(p) => new Date(p?.terms?.closingDatetime).getTime(),
(p) => new Date(p?.datetime).getTime(),
],
['asc', 'asc']
);
const orderByUpgradeBlockHeight = (
arr: ProtocolUpgradeProposalFieldsFragment[]
) =>
orderBy(
arr,
[(p) => p?.upgradeBlockHeight, (p) => p.vegaReleaseTag],
['desc', 'desc']
);
export function getNotRejectedProposals<T extends ProposalFieldsFragment>(
data?: NodeConnection<NodeEdge<T>> | null
): T[] {
@@ -44,7 +24,6 @@ export function getNotRejectedProposals<T extends ProposalFieldsFragment>(
getNodes<ProposalFieldsFragment>(data, (p) =>
p ? p.state !== ProposalState.STATE_REJECTED : false
),
orderByDate,
])(data);
}
@@ -59,7 +38,6 @@ export function getNotRejectedProtocolUpgradeProposals<
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED
: false
),
orderByUpgradeBlockHeight,
])(data);
}
@@ -82,17 +60,14 @@ export const ProposalsContainer = () => {
});
const proposals = useMemo(
() =>
getNotRejectedProposals<ProposalFieldsFragment>(
data?.proposalsConnection
),
() => getNotRejectedProposals(data?.proposalsConnection),
[data]
);
const protocolUpgradeProposals = useMemo(
() =>
protocolUpgradesData
? getNotRejectedProtocolUpgradeProposals<ProtocolUpgradeProposalFieldsFragment>(
? getNotRejectedProtocolUpgradeProposals(
protocolUpgradesData.protocolUpgradeProposals
)
: [],
@@ -49,6 +49,7 @@ export const ProposeFreeform = () => {
formState: { errors },
setValue,
watch,
trigger,
} = useForm<FreeformProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -85,7 +86,13 @@ export const ProposeFreeform = () => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
@@ -89,6 +89,7 @@ export const ProposeNetworkParameter = () => {
formState: { errors },
setValue,
watch,
trigger,
} = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -148,7 +149,13 @@ export const ProposeNetworkParameter = () => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
@@ -61,6 +61,7 @@ export const ProposeNewAsset = () => {
formState: { errors },
setValue,
watch,
trigger,
} = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -117,7 +118,13 @@ export const ProposeNewAsset = () => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
@@ -59,6 +59,7 @@ export const ProposeNewMarket = () => {
formState: { errors },
setValue,
watch,
trigger,
} = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -107,7 +108,13 @@ export const ProposeNewMarket = () => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
@@ -59,6 +59,7 @@ export const ProposeUpdateAsset = () => {
formState: { errors },
setValue,
watch,
trigger,
} = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -107,7 +108,13 @@ export const ProposeUpdateAsset = () => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
@@ -106,6 +106,7 @@ export const ProposeUpdateMarket = () => {
formState: { errors },
setValue,
watch,
trigger,
} = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@@ -157,7 +158,13 @@ export const ProposeUpdateMarket = () => {
await submit(assembleProposal(fields));
};
const viewJson = () => {
const viewJson = async () => {
const isValid = await trigger();
if (!isValid) {
return;
}
const formData = watch();
downloadJson(
JSON.stringify(assembleProposal(formData)),
@@ -16,11 +16,10 @@ const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
arr,
[
(p) => new Date(p?.terms?.enactmentDatetime || 0).getTime(), // has to be defaulted to 0 because new Date(null).getTime() -> NaN which is first when ordered.
(p) => new Date(p?.terms?.closingDatetime).getTime(),
(p) => new Date(p?.terms?.closingDatetime || 0).getTime(), // has to be defaulted to 0 because new Date(null).getTime() -> NaN which is first when ordered
(p) => p.id,
],
['desc', 'desc', 'desc']
['desc', 'desc']
);
export function getRejectedProposals<T extends ProposalFieldsFragment>(
+19 -4
View File
@@ -207,6 +207,13 @@ const LazyWithdrawals = React.lazy(
)
);
const LazyDisclaimer = React.lazy(
() =>
import(
/* webpackChunkName: "route-disclaimer", webpackPrefetch: true */ './disclaimer'
)
);
const redirects = [
{
path: Routes.VALIDATORS,
@@ -224,6 +231,10 @@ const redirects = [
path: '/vesting',
element: <Navigate to={Routes.REDEEM} replace />,
},
{
path: Routes.PROTOCOL_UPGRADES,
element: <Navigate to={Routes.PROPOSALS} replace />,
},
];
const routerConfig = [
@@ -264,13 +275,13 @@ const routerConfig = [
},
{ path: 'proposals', element: <LazyProposalsList /> },
{ path: ':proposalId', element: <LazyProposal /> },
{
path: 'protocol-upgrade/:proposalReleaseTag',
element: <LazyProtocolUpgradeProposal />,
},
{ path: 'rejected', element: <LazyRejectedProposalsList /> },
],
},
{
path: `${Routes.PROTOCOL_UPGRADES}/:proposalReleaseTag`,
element: <LazyProtocolUpgradeProposal />,
},
{
path: Routes.VALIDATORS,
element: <LazyStaking name="Staking" />,
@@ -345,6 +356,10 @@ const routerConfig = [
path: Routes.CONTRACTS,
element: <LazyContracts name="Contracts" />,
},
{
path: Routes.DISCLAIMER,
element: <LazyDisclaimer name="Disclaimer" />,
},
{
path: '*',
// Not lazy as loaded when a user first hits the site
+2
View File
@@ -5,6 +5,7 @@ const Routes = {
REWARDS: '/rewards',
PROPOSALS: '/proposals',
PROPOSALS_REJECTED: '/proposals/rejected',
PROTOCOL_UPGRADES: '/protocol-upgrades',
NOT_PERMITTED: '/not-permitted',
NOT_FOUND: '/not-found',
CONTRACTS: '/contracts',
@@ -14,6 +15,7 @@ const Routes = {
SUPPLY: '/token/tranches',
ASSOCIATE: '/token/associate',
DISASSOCIATE: '/token/disassociate',
DISCLAIMER: '/disclaimer',
};
export default Routes;
@@ -5,12 +5,22 @@ query PreviousEpoch($epochId: ID) {
edges {
node {
id
stakedTotal
rewardScore {
rawValidatorScore
performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
}
rankingScore {
status
previousStatus
rankingScore
stakeScore
performanceScore
votingPower
}
}
}
@@ -8,7 +8,7 @@ export type PreviousEpochQueryVariables = Types.Exact<{
}>;
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string } | null, rankingScore: { __typename?: 'RankingScore', stakeScore: string } } } | null> | null } | null } };
export type PreviousEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, validatorsConnection?: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, stakedTotal: string, rewardScore?: { __typename?: 'RewardScore', rawValidatorScore: string, performanceScore: string, multisigScore: string, validatorScore: string, normalisedScore: string, validatorStatus: Types.ValidatorStatus } | null, rankingScore: { __typename?: 'RankingScore', status: Types.ValidatorStatus, previousStatus: Types.ValidatorStatus, rankingScore: string, stakeScore: string, performanceScore: string, votingPower: string } } } | null> | null } | null } };
export const PreviousEpochDocument = gql`
@@ -19,12 +19,22 @@ export const PreviousEpochDocument = gql`
edges {
node {
id
stakedTotal
rewardScore {
rawValidatorScore
performanceScore
multisigScore
validatorScore
normalisedScore
validatorStatus
}
rankingScore {
status
previousStatus
rankingScore
stakeScore
performanceScore
votingPower
}
}
}
@@ -79,36 +79,72 @@ const MOCK_PREVIOUS_EPOCH: PreviousEpochQuery = {
{
node: {
id: 'ccc022b7e63a4d0a6d3a193c3940c88574060e58a184964c994998d86835a1b4',
stakedTotal: '14182454495731682635157',
rewardScore: {
rawValidatorScore: '0.25',
performanceScore: '0.9998677767864936',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
},
rankingScore: {
stakeScore: '0.2499583402766206',
performanceScore: '0.9998677767864936',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
},
},
},
{
node: {
id: '966438c6bffac737cfb08173ffcb3f393c4692b099ad80cb45a82e2dc0a8cf99',
stakedTotal: '9618711883996159534058',
rewardScore: {
rawValidatorScore: '0.3',
performanceScore: '1',
multisigScore: '',
validatorScore: '0.31067',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
},
rankingScore: {
stakeScore: '0.25',
performanceScore: '0.9998677767864936',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
},
},
},
{
node: {
id: '12c81b738e8051152e1afe44376ec37bca9216466e6d44cdd772194bad0ada81',
stakedTotal: '4041343338923442976709',
rewardScore: {
rawValidatorScore: '0.35',
performanceScore: '0.999629748500531',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
},
rankingScore: {
stakeScore: '0.2312',
performanceScore: '0.9998677767864936',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
},
},
},
@@ -7,12 +7,12 @@ import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
calculateOverallPenalty,
calculateOverstakedPenalty,
calculatesPerformancePenalty,
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
getUnnormalisedVotingPower,
} from '../../shared';
import {
@@ -32,6 +32,7 @@ import type { ValidatorsTableProps } from './shared';
import {
formatNumber,
formatNumberPercentage,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/utils';
import { VALIDATOR_LOGO_MAP } from './logo-map';
@@ -136,6 +137,10 @@ export const ConsensusValidatorsTable = ({
[totalStake]
);
const allNodesInPreviousEpoch = removePaginationWrapper(
previousEpochData?.epoch.validatorsConnection?.edges
);
const nodes = useMemo(() => {
if (!data) return [];
let canonisedNodes = data
@@ -160,7 +165,7 @@ export const ConsensusValidatorsTable = ({
stakedByDelegates,
stakedByOperator,
stakedTotal,
rankingScore: { stakeScore, votingPower },
rankingScore: { stakeScore, votingPower, performanceScore },
pendingStake,
stakedTotalRanking,
stakedByUser,
@@ -172,11 +177,8 @@ export const ConsensusValidatorsTable = ({
: avatarUrl
? avatarUrl
: null;
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
const { rawValidatorScore: previousEpochValidatorScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
return {
id,
@@ -199,21 +201,19 @@ export const ConsensusValidatorsTable = ({
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore(
previousEpochPerformanceScore
).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty(
previousEpochPerformanceScore
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: formatNumberPercentage(
calculatesPerformancePenalty(performanceScore),
2
),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
previousEpochValidatorScore,
previousEpochStakeScore
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
2
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
previousEpochValidatorScore,
previousEpochPerformanceScore,
stakedTotal,
totalStake
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
calculateOverallPenalty(id, allNodesInPreviousEpoch),
2
),
[ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.STAKED_BY_USER]: stakedByUser
@@ -328,12 +328,12 @@ export const ConsensusValidatorsTable = ({
...remaining,
];
}, [
allNodesInPreviousEpoch,
data,
decimals,
hideTopThird,
previousEpochData,
thirdOfTotalStake,
totalStake,
validatorsView,
]);
@@ -5,15 +5,14 @@ import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
calculatesPerformancePenalty,
calculateOverallPenalty,
calculateOverstakedPenalty,
getFormattedPerformanceScore,
getLastEpochScoreAndPerformance,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
} from '../../shared';
import {
defaultColDef,
StakeNeededForPromotionRenderer,
stakedTotalPercentage,
ValidatorFields,
ValidatorRenderer,
@@ -28,6 +27,7 @@ import type { ValidatorsTableProps } from './shared';
import {
formatNumber,
formatNumberPercentage,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/utils';
@@ -52,6 +52,10 @@ export const StandbyPendingValidatorsTable = ({
const gridRef = useRef<AgGridReact | null>(null);
const allNodesInPreviousEpoch = removePaginationWrapper(
previousEpochData?.epoch.validatorsConnection?.edges
);
let nodes = useMemo(() => {
if (!data) return [];
@@ -77,18 +81,15 @@ export const StandbyPendingValidatorsTable = ({
stakedByDelegates,
stakedByOperator,
stakedTotal,
rankingScore: { stakeScore },
rankingScore: { stakeScore, performanceScore },
pendingStake,
stakedTotalRanking,
stakedByUser,
pendingUserStake,
userStakeShare,
}) => {
const {
rawValidatorScore: previousEpochValidatorScore,
performanceScore: previousEpochPerformanceScore,
stakeScore: previousEpochStakeScore,
} = getLastEpochScoreAndPerformance(previousEpochData, id);
const { performanceScore: previousEpochPerformanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription;
@@ -144,21 +145,19 @@ export const StandbyPendingValidatorsTable = ({
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]: getFormattedPerformanceScore(
previousEpochPerformanceScore
).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: getPerformancePenalty(
previousEpochPerformanceScore
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]: formatNumberPercentage(
calculatesPerformancePenalty(performanceScore),
2
),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
previousEpochValidatorScore,
previousEpochStakeScore
[ValidatorFields.OVERSTAKING_PENALTY]: formatNumberPercentage(
calculateOverstakedPenalty(id, allNodesInPreviousEpoch),
2
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
previousEpochValidatorScore,
previousEpochPerformanceScore,
stakedTotal,
totalStake
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
calculateOverallPenalty(id, allNodesInPreviousEpoch),
2
),
[ValidatorFields.PENDING_STAKE]: pendingStake,
[ValidatorFields.STAKED_BY_USER]: stakedByUser
@@ -172,13 +171,13 @@ export const StandbyPendingValidatorsTable = ({
}
);
}, [
allNodesInPreviousEpoch,
data,
decimals,
previousEpochData,
stakeNeededForPromotion,
stakeNeededForPromotionDescription,
t,
totalStake,
]);
if (validatorsView === 'myStake') {
@@ -226,21 +225,21 @@ export const StandbyPendingValidatorsTable = ({
cellRenderer: StakeShareRenderer,
width: 100,
},
{
field: ValidatorFields.STAKE_NEEDED_FOR_PROMOTION,
headerName: t(ValidatorFields.STAKE_NEEDED_FOR_PROMOTION).toString(),
headerTooltip: t(stakeNeededForPromotionDescription, {
prefix: t('The'),
}),
cellRenderer: StakeNeededForPromotionRenderer,
width: 210,
},
// {
// field: ValidatorFields.STAKE_NEEDED_FOR_PROMOTION,
// headerName: t(ValidatorFields.STAKE_NEEDED_FOR_PROMOTION).toString(),
// headerTooltip: t(stakeNeededForPromotionDescription, {
// prefix: t('The'),
// }),
// cellRenderer: StakeNeededForPromotionRenderer,
// width: 210,
// },
{
field: ValidatorFields.TOTAL_PENALTIES,
headerName: t(ValidatorFields.TOTAL_PENALTIES).toString(),
headerTooltip: t('TotalPenaltiesDescription').toString(),
cellRenderer: TotalPenaltiesRenderer,
width: 120,
width: 120 + 210,
},
],
[]
@@ -1,11 +1,15 @@
import React, { useMemo } from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
useEnvironment,
DocsLinks,
ExternalLinks,
} from '@vegaprotocol/environment';
import { toBigNum } from '@vegaprotocol/utils';
import {
formatNumberPercentage,
removePaginationWrapper,
toBigNum,
} from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
import {
Link as UTLink,
@@ -24,11 +28,11 @@ import { SubHeading } from '../../../components/heading';
import {
getLastEpochScoreAndPerformance,
getNormalisedVotingPower,
getOverstakingPenalty,
getPerformancePenalty,
getTotalPenalties,
getUnnormalisedVotingPower,
getStakePercentage,
calculatesPerformancePenalty,
calculateOverstakedPenalty,
calculateOverallPenalty,
} from '../shared';
import type { ReactNode } from 'react';
import type { StakingNodeFieldsFragment } from '../__generated__/Staking';
@@ -78,17 +82,27 @@ export const ValidatorTable = ({
const stakedOnNode = toBigNum(node.stakedTotal, decimals);
const { rawValidatorScore, performanceScore, stakeScore } =
getLastEpochScoreAndPerformance(previousEpochData, node.id);
const { rawValidatorScore } = getLastEpochScoreAndPerformance(
previousEpochData,
node.id
);
const stakePercentage = getStakePercentage(total, stakedOnNode);
const totalPenaltiesAmount = getTotalPenalties(
rawValidatorScore,
performanceScore,
stakedOnNode.toString(),
total.toString()
);
const penalties = useMemo(() => {
const allNodesInPreviousEpoch = removePaginationWrapper(
previousEpochData?.epoch.validatorsConnection?.edges
);
return {
// current epoch
performance: calculatesPerformancePenalty(
node.rankingScore.performanceScore
),
// previous epoch
overstaked: calculateOverstakedPenalty(node.id, allNodesInPreviousEpoch),
overall: calculateOverallPenalty(node.id, allNodesInPreviousEpoch),
};
}, [node, previousEpochData?.epoch.validatorsConnection?.edges]);
return (
<>
@@ -242,7 +256,7 @@ export const ValidatorTable = ({
<Tooltip description={t('OverstakedPenaltyDescription')}>
<span data-testid="overstaking-penalty">
{getOverstakingPenalty(rawValidatorScore, stakeScore)}
{formatNumberPercentage(penalties.overstaked, 2)}
</span>
</Tooltip>
</KeyValueTableRow>
@@ -251,7 +265,7 @@ export const ValidatorTable = ({
<Tooltip description={t('PerformancePenaltyDescription')}>
<span data-testid="performance-penalty">
{getPerformancePenalty(performanceScore)}
{formatNumberPercentage(penalties.performance, 2)}
</span>
</Tooltip>
</KeyValueTableRow>
@@ -260,7 +274,7 @@ export const ValidatorTable = ({
<strong>{t('TOTAL PENALTIES')}</strong>
</span>
<span data-testid="total-penalties">
<strong>{totalPenaltiesAmount}</strong>
<strong>{formatNumberPercentage(penalties.overall, 2)}</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
@@ -9,6 +9,7 @@ import {
getTotalPenalties,
getStakePercentage,
} from './shared';
import * as Schema from '@vegaprotocol/types';
describe('getLastEpochScoreAndPerformance', () => {
const mockPreviousEpochData = {
@@ -19,24 +20,48 @@ describe('getLastEpochScoreAndPerformance', () => {
{
node: {
id: '0x123',
stakedTotal: '',
rewardScore: {
rawValidatorScore: '0.25',
performanceScore: '0.75',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
},
rankingScore: {
stakeScore: '0.25',
performanceScore: '0.75',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
},
},
},
{
node: {
id: '0x234',
stakedTotal: '',
rewardScore: {
rawValidatorScore: '0.35',
performanceScore: '0.85',
multisigScore: '',
validatorScore: '',
normalisedScore: '',
validatorStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
},
rankingScore: {
stakeScore: '0.25',
performanceScore: '0.85',
status: Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
previousStatus:
Schema.ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
rankingScore: '',
votingPower: '',
},
},
},
+89 -1
View File
@@ -4,6 +4,94 @@ import {
} from '@vegaprotocol/utils';
import type { PreviousEpochQuery } from './__generated__/PreviousEpoch';
import { BigNumber } from '../../lib/bignumber';
import type { LastArrayElement } from 'type-fest';
type Node = NonNullable<
LastArrayElement<
NonNullable<
NonNullable<PreviousEpochQuery['epoch']['validatorsConnection']>['edges']
>
>
>['node'];
/**
* Calculates theoretical stake score for a given node
* @param nodeId Id of a node for which a score is calculated
* @param nodes A collection of all nodes
* @returns Theoretical stake score for given node based on the staked total
* of all node of the same type (status)
*/
const calculateTheoreticalStakeScore = (nodeId: string, nodes: Node[]) => {
const node = nodes.find((n) => n.id === nodeId);
if (!node) {
return new BigNumber(0);
}
const all = nodes
.filter((n) => n.rankingScore.status === node.rankingScore.status)
.map((n) => new BigNumber(n.stakedTotal));
const sumOfSameType = all.reduce((acc, a) => acc.plus(a), new BigNumber(0));
if (sumOfSameType.isZero()) {
return new BigNumber(0);
}
return new BigNumber(node.stakedTotal).dividedBy(sumOfSameType);
};
/**
* Calculates overall penalty for a given node
* @param nodeId Id of a node for which a penalty is calculated
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
* @returns %
*/
export const calculateOverallPenalty = (nodeId: string, nodes: Node[]) => {
const node = nodes.find((n) => n.id === nodeId);
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
if (!node || tts.isZero()) {
return new BigNumber(0);
}
const penalty = new BigNumber(1)
.minus(new BigNumber(node.rewardScore?.validatorScore || 0).dividedBy(tts))
.times(100);
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
};
/**
* Calculates over-staked penalty for a given node
* @param nodeId Id of a node for which a penalty is calculated
* @param nodes A collection of all nodes - needed to calculate theoretical stake score
* @returns %
*/
export const calculateOverstakedPenalty = (nodeId: string, nodes: Node[]) => {
const node = nodes.find((n) => n.id === nodeId);
const tts = calculateTheoreticalStakeScore(nodeId, nodes);
if (!node || tts.isZero()) {
return new BigNumber(0);
}
const penalty = new BigNumber(1)
.minus(
new BigNumber(node.rewardScore?.rawValidatorScore || 0).dividedBy(tts)
)
.times(100);
console.log(
nodeId,
new BigNumber(node.rewardScore?.rawValidatorScore || 0).toString(),
tts.toString(),
new BigNumber(node.rewardScore?.rawValidatorScore || 0)
.dividedBy(tts)
.toString()
);
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
};
/**
* Calculates performance penalty based on the given performance score.
* @returns %
*/
export const calculatesPerformancePenalty = (performanceScore: string) => {
const penalty = new BigNumber(1)
.minus(new BigNumber(performanceScore))
.times(100);
return penalty.isLessThan(0) ? new BigNumber(0) : penalty;
};
export const getLastEpochScoreAndPerformance = (
previousEpochData: PreviousEpochQuery | undefined,
@@ -15,7 +103,7 @@ export const getLastEpochScoreAndPerformance = (
return {
rawValidatorScore: validator?.rewardScore?.rawValidatorScore,
performanceScore: validator?.rewardScore?.performanceScore,
performanceScore: validator?.rankingScore?.performanceScore,
stakeScore: validator?.rankingScore?.stakeScore,
};
};
File diff suppressed because it is too large Load Diff
@@ -82,20 +82,12 @@ const generateProposal = (code: string): ProposalListFieldsFragment => ({
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionInternal',
sourceType: {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [],
},
},
},
dataSourceSpecForTradingTermination: {
__typename: 'DataSourceDefinition',
sourceType: {
__typename: 'DataSourceDefinitionInternal',
sourceType: {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [],
},
},
},
},
+13 -1
View File
@@ -1,7 +1,7 @@
import { mockConnectWallet } from '@vegaprotocol/cypress';
describe('Navbar', { tags: '@smoke' }, () => {
before(() => {
beforeEach(() => {
cy.clearAllLocalStorage();
cy.mockTradingPage();
cy.mockSubscription();
@@ -47,6 +47,18 @@ describe('Navbar', { tags: '@smoke' }, () => {
});
});
});
it('Disclaimer should be presented after choosing from menu', () => {
cy.get('nav')
.find('ul li:contains(Resources)')
.contains('Resources')
.click();
cy.getByTestId('Disclaimer').eq(0).click();
cy.location('hash').should('equal', '#/disclaimer');
cy.get('p').contains(
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets.'
);
});
});
describe('mobile view', () => {
@@ -482,7 +482,7 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
testOrderCancellation(order);
});
});
it('must be able to cancel all orders on a market', () => {
it('must be able to cancel all orders on all markets', () => {
// 7003-MORD-009
// 7003-MORD-010
// 7003-MORD-011
@@ -496,9 +496,7 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
.should('have.text', 'Cancel all')
.then(($btn) => {
cy.wrap($btn).click({ force: true });
const order: OrderCancellation = {
marketId: 'market-0',
};
const order: OrderCancellation = {};
testOrderCancellation(order);
});
});
@@ -0,0 +1,48 @@
import { t } from '@vegaprotocol/i18n';
export const Disclaimer = () => {
return (
<div className="py-16 px-8 flex w-full justify-center">
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
{t('Disclaimer')}
</h1>
<p className="mb-6 mt-10">
{t(
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets. The Vega Protocol is an implementation layer (layer one) protocol made of free, public, open-source or source-available software. Use of the Vega Protocol involves various risks, including but not limited to, losses while digital assets are supplied to the Vega Protocol and losses due to the fluctuation of prices of assets.'
)}
</p>
<p className="mb-6">
{t(
'Before using the Vega Protocol, review the relevant documentation at docs.vega.xyz to make sure that you understand how it works. Conduct your own due diligence and consult your financial advisor before making any investment decisions.'
)}
</p>
<p className="mb-6">
{t(
'As described in the Vega Protocol core license, the Vega Protocol is provided “as is”, at your own risk, and without warranties of any kind. Although Gobalsky Labs Limited developed much of the initial code for the Vega Protocol, it does not provide or control the Vega Protocol, which is run by third parties deploying it on a bespoke blockchain. Upgrades and modifications to the Vega Protocol are managed in a community-driven way by holders of the VEGA governance token.'
)}
</p>
<p className="mb-8">
{t(
'No developer or entity involved in creating the Vega Protocol will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Vega Protocol, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or legal costs, or loss of profits, cryptoassets, tokens or anything else of value.'
)}
</p>
<p className="mb-8">
{t(
'This website is hosted on a decentralised network, the Interplanetary File System (“IPFS”). The IPFS decentralised web is made up of all the computers (nodes) connected to it. Data is therefore stored on many different computers.'
)}
</p>
<p className="mb-8">
{t(
"The information provided on this website does not constitute investment advice, financial advice, trading advice, or any other sort of advice and you should not treat any of the website's content as such. No party recommends that any cryptoasset should be bought, sold, or held by you via this website. No party ensures the accuracy of information listed on this website or holds any responsibility for any missing or wrong information. You understand that you are using any and all information available here at your own risk."
)}
</p>
<p className="mb-8">
{t(
'Additionally, just as you can access email protocols such as SMTP through multiple email clients, you can potentially access the Vega Protocol through many web or mobile interfaces. You are responsible for doing your own diligence on those interfaces to understand the associated risks and any fees.'
)}
</p>
</div>
</div>
);
};
@@ -0,0 +1,3 @@
import { Disclaimer } from './disclaimer';
export default Disclaimer;
+10 -3
View File
@@ -36,9 +36,8 @@ export const Navbar = ({
}) => {
const { GITHUB_FEEDBACK_URL } = useEnvironment();
const tokenLink = useLinks(DApp.Token);
const { marketId } = useGlobalStore((store) => ({
marketId: store.marketId,
}));
const marketId = useGlobalStore((store) => store.marketId);
const tradingPath = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKET]();
@@ -107,6 +106,14 @@ export const Navbar = ({
{t('Give Feedback')}
</NavExternalLink>
</NavigationItem>
<NavigationItem>
<NavigationLink
data-testid="Disclaimer"
to={Links[Routes.DISCLAIMER]()}
>
{t('Disclaimer')}
</NavigationLink>
</NavigationItem>
</NavigationList>
</NavigationContent>
</NavigationItem>
@@ -1 +1,2 @@
export * from './welcome-dialog';
export * from './risk-message';
@@ -0,0 +1,45 @@
import { t } from '@vegaprotocol/i18n';
import {
ExternalLink,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
export const RiskMessage = () => {
return (
<>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
<ul className="list-[square] ml-4">
<li className="mb-1">
{t(
'Conduct your own due diligence and consult your financial advisor before making any investment decisions.'
)}
</li>
<li className="mb-1">
{t(
'You may encounter bugs, loss of functionality or loss of assets.'
)}
</li>
<li>
{t('No party accepts any liability for any losses whatsoever.')}
</li>
</ul>
</div>
<p className="mb-8">
{t(
'By using the Vega Console, you acknowledge that you have read and understood the'
)}{' '}
<ExternalLink
href={`/#/${Links[Routes.DISCLAIMER]()}`}
className="underline"
>
<span className="flex items-center gap-1">
<span>{t('Vega Console Disclaimer')}</span>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</span>
</ExternalLink>
</p>
</>
);
};
@@ -47,25 +47,4 @@ describe('Risk notice dialog', () => {
expect(mockOnClose).toHaveBeenCalled();
}
);
it('displays a risk message for mainnet', () => {
const introText = 'Regulation may apply to use of this app';
const network = Networks.MAINNET;
// @ts-ignore ignore mock implementation
useEnvironment.mockImplementation(() => ({
...mockEnvDefinitions,
VEGA_ENV: network,
}));
render(<RiskNoticeDialog onClose={mockOnClose} network={network} />);
expect(screen.getByText(introText)).toBeInTheDocument();
const button = screen.getByRole('button', {
name: 'I understand, Continue',
});
fireEvent.click(button);
expect(mockOnClose).toHaveBeenCalled();
});
});
@@ -7,8 +7,8 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { RISK_ACCEPTED_KEY } from '../constants';
import { TelemetryApproval } from './telemetry-approval';
import type { Networks } from '@vegaprotocol/environment';
import {
Networks,
useEnvironment,
DocsLinks,
ExternalLinks,
@@ -28,29 +28,17 @@ export const RiskNoticeDialog = ({ onClose, network }: Props) => {
};
return (
<>
{network === Networks.MAINNET ? (
<MainnetContent />
) : (
<TestnetContent network={network} />
)}
<div className="my-4">
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
</div>
<Button onClick={handleAcceptRisk}>
{network === Networks.MAINNET
? t('I understand, Continue')
: t('Continue')}
</Button>
</>
<TestnetContent network={network} handleAcceptRisk={handleAcceptRisk} />
);
};
const TestnetContent = ({ network }: { network: Networks }) => {
const TestnetContent = ({
network,
handleAcceptRisk,
}: {
network: Networks;
handleAcceptRisk: () => void;
}) => {
const { GITHUB_FEEDBACK_URL } = useEnvironment();
return (
<>
@@ -88,29 +76,14 @@ const TestnetContent = ({ network }: { network: Networks }) => {
</li>
</ul>
)}
</>
);
};
const MainnetContent = () => {
return (
<>
<h4 className="text-xl mb-2 mt-4">
{t('Regulation may apply to use of this app')}
</h4>
<p className="mb-6">
{t(
'This decentralised application allows you to connect to and use publicly available blockchain services operated by third parties that may include trading, financial products, or other services that may be subject to legal and regulatory restrictions in your jurisdiction. This application is a front end only and does not hold any funds or provide any products or services. It is available to anyone with an internet connection via IPFS and other methods, and the ability to access it does not imply any right to use any services or that it is legal for you to do so. By using this application you accept that it is your responsibility to ensure that your use of the application and any blockchain services accessed through it is compliant with applicable laws and regulations in your jusrisdiction.'
)}
</p>
<h4 className="text-xl mb-2">
{t('Technical and financial risk of loss')}
</h4>
<p className="mb-8">
{t(
'The public blockchain services accessible via this decentralised application are operated by third parties and may carry significant risks including the potential loss of all funds that you deposit or hold with these services. Technical risks include the risk of loss in the event of the failure or compromise of the public blockchain infrastructure or smart contracts that provide any services you use. Financial risks include but are not limited to losses due to volatility, excessive leverage, low liquidity, and your own lack of understanding of the services you use. By using this decentralised application you accept that it is your responsibility to ensure that you understand any services you use and the technical and financial risks inherent in your use. Do not risk what you cannot afford to lose.'
)}
</p>
<div className="my-4">
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
</div>
<Button onClick={handleAcceptRisk}>{t('Continue')}</Button>
</>
);
};
@@ -5,7 +5,7 @@ import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
const [isApproved, setIsApproved] = useTelemetryApproval();
return (
<div className="flex flex-col px-2 py-3">
<div className="flex flex-col py-3">
<div className="mr-4" role="form">
<Checkbox
label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
@@ -30,17 +30,24 @@ export const WelcomeDialog = () => {
const shouldDisplayWelcomeDialog = useGlobalStore(
(store) => store.shouldDisplayWelcomeDialog
);
const isRiskDialogNeeded = riskAccepted !== 'true' && !('Cypress' in window);
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
const onCloseDialog = useCallback(() => {
update({ shouldDisplayWelcomeDialog: isRiskDialogNeeded });
}, [update, isRiskDialogNeeded]);
const isRiskDialogNeeded =
riskAccepted !== 'true' &&
VEGA_ENV !== Networks.MAINNET &&
!('Cypress' in window);
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
const onCloseDialog = useCallback(() => {
update({
shouldDisplayWelcomeDialog: isRiskDialogNeeded,
});
}, [update, isRiskDialogNeeded]);
if (isRiskDialogNeeded) {
dialogContent = (
<RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} />
);
title = VEGA_ENV === Networks.MAINNET ? t('WARNING') : t('Vega Console');
title = t('Vega Console');
size = 'medium';
} else if (isWelcomeDialogNeeded && data?.length === 0) {
dialogContent = <WelcomeNoticeDialog />;
@@ -16,6 +16,8 @@ import { Link } from 'react-router-dom';
import { ProposedMarkets } from './proposed-markets';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { TelemetryApproval } from './telemetry-approval';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
export const SelectMarketLandingTable = ({
markets,
@@ -94,6 +96,8 @@ export const WelcomeLandingDialog = ({
onClose,
}: LandingDialogContainerProps) => {
const { data, loading, error } = useMarketList();
const { VEGA_ENV } = useEnvironment();
const isMainnet = VEGA_ENV === Networks.MAINNET;
if (error) {
return (
<div className="flex justify-center items-center">
@@ -114,6 +118,13 @@ export const WelcomeLandingDialog = ({
<>
<WelcomeDialogHeader />
<SelectMarketLandingTable markets={data} onClose={onClose} />
{isMainnet && (
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
)}
</>
);
};
@@ -10,6 +10,7 @@ import {
ExternalLinks,
} from '@vegaprotocol/environment';
import { ProposedMarkets } from './proposed-markets';
import { TelemetryApproval } from './telemetry-approval';
export const WelcomeNoticeDialog = () => {
const { VEGA_ENV } = useEnvironment();
@@ -20,30 +21,30 @@ export const WelcomeNoticeDialog = () => {
<>
<h1
data-testid="welcome-notice-title"
className="mb-6 p-4 text-center text-2xl"
className="text-2xl uppercase mb-6 text-center font-alpha calt"
>
{t('Welcome to Console')}
</h1>
<p className="leading-6 mb-7">
<p className="leading-6 mb-4">
{t(
'There are no markets to trade on right now. Trading on Vega is now live, but markets need to pass a governance vote before they can be traded on. In the meantime:'
)}
</p>
<ul className="list-[square] pl-7">
<ul className="list-[square] pl-4 mb-4">
{isMainnet && (
<li>
<li className="mb-1">
<ExternalLink target="_blank" href={consoleFairgroundLink()}>
{t('Try out Console')}
</ExternalLink>
{t(' on Fairground, our Testnet')}
</li>
)}
<li>
<li className="mb-1">
<ExternalLink target="_blank" href={tokenLink(TOKEN_PROPOSALS)}>
{t('View and vote for proposed markets')}
</ExternalLink>
</li>
<li>
<li className="mb-1">
<ExternalLink
target="_blank"
href={tokenLink(TOKEN_NEW_MARKET_PROPOSAL)}
@@ -51,12 +52,19 @@ export const WelcomeNoticeDialog = () => {
{t('Propose a market')}
</ExternalLink>
</li>
<li>
<li className="mb-1">
<ExternalLink target="_blank" href={ExternalLinks.BLOG}>
{t('Read about the mainnet launch')}
</ExternalLink>
</li>
</ul>
{isMainnet && (
<TelemetryApproval
helpText={t(
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
)}
/>
)}
<ProposedMarkets />
</>
);
+10
View File
@@ -30,6 +30,10 @@ const LazySettings = dynamic(() => import('../client-pages/settings'), {
ssr: false,
});
const LazyDisclaimer = dynamic(() => import('../client-pages/disclaimer'), {
ssr: false,
});
export enum Routes {
HOME = '/',
MARKET = '/markets',
@@ -37,6 +41,7 @@ export enum Routes {
PORTFOLIO = '/portfolio',
LIQUIDITY = 'liquidity/:marketId',
SETTINGS = 'settings',
DISCLAIMER = 'disclaimer',
}
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
@@ -51,6 +56,7 @@ export const Links: ConsoleLinks = {
? trimEnd(`${Routes.LIQUIDITY}/${marketId}`, '/')
: Routes.LIQUIDITY,
[Routes.SETTINGS]: () => Routes.SETTINGS,
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
};
const routerConfig: RouteObject[] = [
@@ -97,6 +103,10 @@ const routerConfig: RouteObject[] = [
path: Routes.SETTINGS,
element: <LazySettings />,
},
{
path: Routes.DISCLAIMER,
element: <LazyDisclaimer />,
},
{
path: '*',
element: (
+5 -2
View File
@@ -12,13 +12,16 @@ import {
} from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts';
import { RiskMessage } from '../components/welcome-dialog';
const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
return (
<>
<VegaConnectDialog connectors={Connectors} />
<VegaConnectDialog
connectors={Connectors}
riskMessage={<RiskMessage />}
/>
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
+13 -1
View File
@@ -115,7 +115,19 @@ export function createClient({
)
: httpLink;
const errorLink = onError(({ graphQLErrors, networkError }) => {
const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
// if any of these queries error don't capture any errors, its
// likely the user is connecting to a dodgy node, NodeGuard gets
// called on startup, NodeCheck and NodeCheckTimeUpdate are called
// by the NodeSwitcher component and the useNodeHealth hook
if (
['NodeGuard', 'NodeCheck', 'NodeCheckTimeUpdate'].includes(
operation.operationName
)
) {
return;
}
if (graphQLErrors) {
graphQLErrors.forEach((e) => {
if (e.extensions && e.extensions['type'] !== NOT_FOUND) {
@@ -60,6 +60,7 @@ export function addVegaWalletConnect() {
export function addSetVegaWallet() {
Cypress.Commands.add('setVegaWallet', () => {
cy.window().then((win) => {
win.localStorage.setItem('vega_risk_accepted', 'true');
win.localStorage.setItem(
'vega_wallet_config',
JSON.stringify({
@@ -3,11 +3,13 @@ import { MockedProvider } from '@apollo/react-testing';
import { act, render, screen, waitFor } from '@testing-library/react';
import { RadioGroup } from '@vegaprotocol/ui-toolkit';
import type {
BlockTimeSubscription,
StatisticsQuery,
} from '../../utils/__generated__/Node';
import { BlockTimeDocument } from '../../utils/__generated__/Node';
import { StatisticsDocument } from '../../utils/__generated__/Node';
NodeCheckTimeUpdateSubscription,
NodeCheckQuery,
} from '../../utils/__generated__/NodeCheck';
import {
NodeCheckDocument,
NodeCheckTimeUpdateDocument,
} from '../../utils/__generated__/NodeCheck';
import type { RowDataProps } from './row-data';
import { POLL_INTERVAL } from './row-data';
import { BLOCK_THRESHOLD, RowData } from './row-data';
@@ -19,9 +21,9 @@ jest.mock('@vegaprotocol/apollo-client', () => ({
useHeaderStore: jest.fn().mockReturnValue({}),
}));
const statsQueryMock: MockedResponse<StatisticsQuery> = {
const statsQueryMock: MockedResponse<NodeCheckQuery> = {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: {
@@ -34,9 +36,9 @@ const statsQueryMock: MockedResponse<StatisticsQuery> = {
},
};
const subMock: MockedResponse<BlockTimeSubscription> = {
const subMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
request: {
query: BlockTimeDocument,
query: NodeCheckTimeUpdateDocument,
},
result: {
data: {
@@ -71,8 +73,8 @@ const mockHeaders = (
const renderComponent = (
props: RowDataProps,
queryMock: MockedResponse<StatisticsQuery>,
subMock: MockedResponse<BlockTimeSubscription>
queryMock: MockedResponse<NodeCheckQuery>,
subMock: MockedResponse<NodeCheckTimeUpdateSubscription>
) => {
return (
<MockedProvider mocks={[queryMock, subMock, subMock, subMock]}>
@@ -127,16 +129,16 @@ describe('RowData', () => {
it('radio button still enabled if query fails', async () => {
mockHeaders(props.url, {});
const failedQueryMock: MockedResponse<StatisticsQuery> = {
const failedQueryMock: MockedResponse<NodeCheckQuery> = {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
error: new Error('failed'),
};
const failedSubMock: MockedResponse<BlockTimeSubscription> = {
const failedSubMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
request: {
query: BlockTimeDocument,
query: NodeCheckTimeUpdateDocument,
},
error: new Error('failed'),
};
@@ -244,10 +246,10 @@ describe('RowData', () => {
jest.useFakeTimers();
const createStatsQueryMock = (
blockHeight: string
): MockedResponse<StatisticsQuery> => {
): MockedResponse<NodeCheckQuery> => {
return {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: {
@@ -261,10 +263,10 @@ describe('RowData', () => {
};
};
const createFailedStatsQueryMock = (): MockedResponse<StatisticsQuery> => {
const createFailedStatsQueryMock = (): MockedResponse<NodeCheckQuery> => {
return {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: undefined,
@@ -6,9 +6,9 @@ import { Radio } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react';
import { CUSTOM_NODE_KEY } from '../../types';
import {
useBlockTimeSubscription,
useStatisticsQuery,
} from '../../utils/__generated__/Node';
useNodeCheckQuery,
useNodeCheckTimeUpdateSubscription,
} from '../../utils/__generated__/NodeCheck';
import { LayoutCell } from './layout-cell';
export const POLL_INTERVAL = 1000;
@@ -30,13 +30,14 @@ export const RowData = ({
const [subFailed, setSubFailed] = useState(false);
const [time, setTime] = useState<number>();
// no use of data here as we need the data nodes reference to block height
const { data, error, loading, startPolling, stopPolling } =
useStatisticsQuery({
const { data, error, loading, startPolling, stopPolling } = useNodeCheckQuery(
{
pollInterval: POLL_INTERVAL,
// fix for pollInterval
// https://github.com/apollographql/apollo-client/issues/9819
ssr: false,
});
}
);
const headerStore = useHeaderStore();
const headers = headerStore[url];
@@ -44,7 +45,7 @@ export const RowData = ({
data: subData,
error: subError,
loading: subLoading,
} = useBlockTimeSubscription();
} = useNodeCheckTimeUpdateSubscription();
useEffect(() => {
const timeout = setTimeout(() => {
@@ -1,11 +1,11 @@
import {
StatisticsDocument,
BlockTimeDocument,
} from '../../utils/__generated__/Node';
NodeCheckDocument,
NodeCheckTimeUpdateDocument,
} from '../../utils/__generated__/NodeCheck';
import type {
StatisticsQuery,
BlockTimeSubscription,
} from '../../utils/__generated__/Node';
NodeCheckQuery,
NodeCheckTimeUpdateSubscription,
} from '../../utils/__generated__/NodeCheck';
import { Networks } from '../../types';
import type { RequestHandlerResponse } from 'mock-apollo-client';
import { createMockClient } from 'mock-apollo-client';
@@ -21,7 +21,7 @@ type MockClientProps = {
busEvents?: MockRequestConfig;
};
export const getMockBusEventsResult = (): BlockTimeSubscription => ({
export const getMockBusEventsResult = (): NodeCheckTimeUpdateSubscription => ({
busEvents: [
{
__typename: 'BusEvent',
@@ -32,19 +32,21 @@ export const getMockBusEventsResult = (): BlockTimeSubscription => ({
export const getMockStatisticsResult = (
env: Networks = Networks.TESTNET
): StatisticsQuery => ({
): NodeCheckQuery => ({
statistics: {
__typename: 'Statistics',
chainId: `${env.toLowerCase()}-0123`,
blockHeight: '11',
vegaTime: new Date().toISOString(),
},
});
export const getMockQueryResult = (env: Networks): StatisticsQuery => ({
export const getMockQueryResult = (env: Networks): NodeCheckQuery => ({
statistics: {
__typename: 'Statistics',
chainId: `${env.toLowerCase()}-0123`,
blockHeight: '11',
vegaTime: new Date().toISOString(),
},
});
@@ -72,11 +74,11 @@ export default function ({
const mockClient = createMockClient();
mockClient.setRequestHandler(
StatisticsDocument,
NodeCheckDocument,
getHandler(statistics, getMockStatisticsResult(network))
);
mockClient.setRequestHandler(
BlockTimeDocument,
NodeCheckTimeUpdateDocument,
getHandler(busEvents, getMockBusEventsResult())
);
+11 -9
View File
@@ -5,11 +5,13 @@ import { useEffect } from 'react';
import { create } from 'zustand';
import { createClient } from '@vegaprotocol/apollo-client';
import type {
BlockTimeSubscription,
StatisticsQuery,
} from '../utils/__generated__/Node';
import { BlockTimeDocument } from '../utils/__generated__/Node';
import { StatisticsDocument } from '../utils/__generated__/Node';
NodeCheckTimeUpdateSubscription,
NodeCheckQuery,
} from '../utils/__generated__/NodeCheck';
import {
NodeCheckDocument,
NodeCheckTimeUpdateDocument,
} from '../utils/__generated__/NodeCheck';
import type { Environment } from '../types';
import { Networks } from '../types';
import { compileErrors } from '../utils/compile-errors';
@@ -220,8 +222,8 @@ const testNode = async (
*/
const testQuery = async (client: Client) => {
try {
const result = await client.query<StatisticsQuery>({
query: StatisticsDocument,
const result = await client.query<NodeCheckQuery>({
query: NodeCheckDocument,
});
if (!result || result.error) {
return false;
@@ -240,8 +242,8 @@ const testQuery = async (client: Client) => {
const testSubscription = (client: Client) => {
return new Promise((resolve) => {
const sub = client
.subscribe<BlockTimeSubscription>({
query: BlockTimeDocument,
.subscribe<NodeCheckTimeUpdateSubscription>({
query: NodeCheckTimeUpdateDocument,
errorPolicy: 'all',
})
.subscribe({
@@ -2,8 +2,8 @@ import { renderHook, waitFor } from '@testing-library/react';
import { useNodeHealth } from './use-node-health';
import type { MockedResponse } from '@apollo/react-testing';
import { MockedProvider } from '@apollo/react-testing';
import type { StatisticsQuery } from '../utils/__generated__/Node';
import { StatisticsDocument } from '../utils/__generated__/Node';
import type { NodeCheckQuery } from '../utils/__generated__/NodeCheck';
import { NodeCheckDocument } from '../utils/__generated__/NodeCheck';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { Intent } from '@vegaprotocol/ui-toolkit';
@@ -16,10 +16,10 @@ jest.mock('@vegaprotocol/apollo-client');
const createStatsMock = (
blockHeight: number
): MockedResponse<StatisticsQuery> => {
): MockedResponse<NodeCheckQuery> => {
return {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: {
@@ -34,7 +34,7 @@ const createStatsMock = (
};
function setup(
mock: MockedResponse<StatisticsQuery>,
mock: MockedResponse<NodeCheckQuery>,
headers:
| {
blockHeight: number;
@@ -93,9 +93,9 @@ describe('useNodeHealth', () => {
);
it('block diff is null if query fails indicating non operational', async () => {
const failedQuery: MockedResponse<StatisticsQuery> = {
const failedQuery: MockedResponse<NodeCheckQuery> = {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
// @ts-ignore failed query with no result
@@ -1,5 +1,5 @@
import { useEffect, useMemo } from 'react';
import { useStatisticsQuery } from '../utils/__generated__/Node';
import { useNodeCheckQuery } from '../utils/__generated__/NodeCheck';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { useEnvironment } from './use-environment';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
@@ -16,7 +16,7 @@ export const useNodeHealth = () => {
const url = useEnvironment((store) => store.VEGA_URL);
const headerStore = useHeaderStore();
const headers = url ? headerStore[url] : undefined;
const { data, error, startPolling, stopPolling } = useStatisticsQuery({
const { data, error, startPolling, stopPolling } = useNodeCheckQuery({
fetchPolicy: 'no-cache',
});
+1 -1
View File
@@ -8,4 +8,4 @@ export * from './hooks';
export * from './types';
// Utils
export * from './utils/__generated__/Node';
export * from './utils/__generated__/NodeCheck';
@@ -1,4 +1,4 @@
query Statistics {
query NodeCheck {
statistics {
chainId
blockHeight
@@ -6,7 +6,7 @@ query Statistics {
}
}
subscription BlockTime {
subscription NodeCheckTimeUpdate {
busEvents(types: TimeUpdate, batchSize: 1) {
id
}
-81
View File
@@ -1,81 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type StatisticsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type StatisticsQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any } };
export type BlockTimeSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
export type BlockTimeSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', id: string }> | null };
export const StatisticsDocument = gql`
query Statistics {
statistics {
chainId
blockHeight
vegaTime
}
}
`;
/**
* __useStatisticsQuery__
*
* To run a query within a React component, call `useStatisticsQuery` and pass it any options that fit your needs.
* When your component renders, `useStatisticsQuery` 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 } = useStatisticsQuery({
* variables: {
* },
* });
*/
export function useStatisticsQuery(baseOptions?: Apollo.QueryHookOptions<StatisticsQuery, StatisticsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<StatisticsQuery, StatisticsQueryVariables>(StatisticsDocument, options);
}
export function useStatisticsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<StatisticsQuery, StatisticsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<StatisticsQuery, StatisticsQueryVariables>(StatisticsDocument, options);
}
export type StatisticsQueryHookResult = ReturnType<typeof useStatisticsQuery>;
export type StatisticsLazyQueryHookResult = ReturnType<typeof useStatisticsLazyQuery>;
export type StatisticsQueryResult = Apollo.QueryResult<StatisticsQuery, StatisticsQueryVariables>;
export const BlockTimeDocument = gql`
subscription BlockTime {
busEvents(types: TimeUpdate, batchSize: 1) {
id
}
}
`;
/**
* __useBlockTimeSubscription__
*
* To run a query within a React component, call `useBlockTimeSubscription` and pass it any options that fit your needs.
* When your component renders, `useBlockTimeSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useBlockTimeSubscription({
* variables: {
* },
* });
*/
export function useBlockTimeSubscription(baseOptions?: Apollo.SubscriptionHookOptions<BlockTimeSubscription, BlockTimeSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<BlockTimeSubscription, BlockTimeSubscriptionVariables>(BlockTimeDocument, options);
}
export type BlockTimeSubscriptionHookResult = ReturnType<typeof useBlockTimeSubscription>;
export type BlockTimeSubscriptionResult = Apollo.SubscriptionResult<BlockTimeSubscription>;
+81
View File
@@ -0,0 +1,81 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type NodeCheckQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type NodeCheckQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any } };
export type NodeCheckTimeUpdateSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
export type NodeCheckTimeUpdateSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', id: string }> | null };
export const NodeCheckDocument = gql`
query NodeCheck {
statistics {
chainId
blockHeight
vegaTime
}
}
`;
/**
* __useNodeCheckQuery__
*
* To run a query within a React component, call `useNodeCheckQuery` and pass it any options that fit your needs.
* When your component renders, `useNodeCheckQuery` 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 } = useNodeCheckQuery({
* variables: {
* },
* });
*/
export function useNodeCheckQuery(baseOptions?: Apollo.QueryHookOptions<NodeCheckQuery, NodeCheckQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<NodeCheckQuery, NodeCheckQueryVariables>(NodeCheckDocument, options);
}
export function useNodeCheckLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NodeCheckQuery, NodeCheckQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<NodeCheckQuery, NodeCheckQueryVariables>(NodeCheckDocument, options);
}
export type NodeCheckQueryHookResult = ReturnType<typeof useNodeCheckQuery>;
export type NodeCheckLazyQueryHookResult = ReturnType<typeof useNodeCheckLazyQuery>;
export type NodeCheckQueryResult = Apollo.QueryResult<NodeCheckQuery, NodeCheckQueryVariables>;
export const NodeCheckTimeUpdateDocument = gql`
subscription NodeCheckTimeUpdate {
busEvents(types: TimeUpdate, batchSize: 1) {
id
}
}
`;
/**
* __useNodeCheckTimeUpdateSubscription__
*
* To run a query within a React component, call `useNodeCheckTimeUpdateSubscription` and pass it any options that fit your needs.
* When your component renders, `useNodeCheckTimeUpdateSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useNodeCheckTimeUpdateSubscription({
* variables: {
* },
* });
*/
export function useNodeCheckTimeUpdateSubscription(baseOptions?: Apollo.SubscriptionHookOptions<NodeCheckTimeUpdateSubscription, NodeCheckTimeUpdateSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<NodeCheckTimeUpdateSubscription, NodeCheckTimeUpdateSubscriptionVariables>(NodeCheckTimeUpdateDocument, options);
}
export type NodeCheckTimeUpdateSubscriptionHookResult = ReturnType<typeof useNodeCheckTimeUpdateSubscription>;
export type NodeCheckTimeUpdateSubscriptionResult = Apollo.SubscriptionResult<NodeCheckTimeUpdateSubscription>;
+4 -4
View File
@@ -1,11 +1,11 @@
import type { StatisticsQuery } from './__generated__/Node';
import type { NodeCheckQuery } from './__generated__/NodeCheck';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const statisticsQuery = (
override?: PartialDeep<StatisticsQuery>
): StatisticsQuery => {
const defaultResult: StatisticsQuery = {
override?: PartialDeep<NodeCheckQuery>
): NodeCheckQuery => {
const defaultResult: NodeCheckQuery = {
statistics: {
__typename: 'Statistics',
chainId: 'chain-id',
@@ -16,16 +16,6 @@ fragment DataSource on DataSourceDefinition {
}
}
}
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
}
}
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type DataSourceFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } };
export type DataSourceFragment = { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } };
export type MarketInfoQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null };
export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null } } | { __typename?: 'DataSourceDefinitionInternal' } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null };
export const DataSourceFragmentDoc = gql`
fragment DataSource on DataSourceDefinition {
@@ -31,16 +31,6 @@ export const DataSourceFragmentDoc = gql`
}
}
}
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
operator
value
}
}
}
}
}
}
`;
@@ -105,12 +105,10 @@ export const MarketInfoAccordion = ({
content: <InsurancePoolInfoPanel market={market} account={a} />,
})),
];
const settlementData =
market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData
.data;
const terminationData =
market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data;
const settlementData = market.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const terminationData = market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
@@ -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<
@@ -465,15 +462,17 @@ export const OracleInfoPanel = ({
? product.dataSourceSpecForSettlementData.id
: product.dataSourceSpecForTradingTermination.id;
const dataSourceSpec = (
type === 'settlementData'
? product.dataSourceSpecForSettlementData.data
: product.dataSourceSpecForTradingTermination.data
) as DataSourceDefinition;
return (
<div className="flex flex-col gap-4">
<DataSourceProof
data-testid="oracle-proof-links"
data={
type === 'settlementData'
? product.dataSourceSpecForSettlementData.data
: product.dataSourceSpecForTradingTermination.data
}
data={dataSourceSpec}
providers={data}
type={type}
dataSourceSpecId={dataSourceSpecId}
@@ -530,19 +529,27 @@ export const DataSourceProof = ({
}
if (data.sourceType.__typename === 'DataSourceDefinitionInternal') {
return (
<div>
<h3>{t('Internal conditions')}</h3>
{data.sourceType.sourceType.conditions.map((condition, i) => {
if (!condition) return null;
return (
<p key={i}>
{ConditionOperatorMapping[condition.operator]} {condition.value}
</p>
);
})}
</div>
);
if (data.sourceType.sourceType) {
return (
<div>
<h3>{t('Internal conditions')}</h3>
{data.sourceType.sourceType?.conditions.map((condition, i) => {
if (!condition) return null;
return (
<p key={i}>
{ConditionOperatorMapping[condition.operator]} {condition.value}
</p>
);
})}
</div>
);
} else {
return (
<div>
{t('No oracle spec for trading termination. Internal timestamp used')}
</div>
);
}
}
return <div>{t('Invalid data source')}</div>;
@@ -613,34 +620,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';

Some files were not shown because too many files have changed in this diff Show More