diff --git a/.github/workflows/capsule-cypress.yml b/.github/workflows/capsule-cypress.yml index f60584ae9..0414d9258 100644 --- a/.github/workflows/capsule-cypress.yml +++ b/.github/workflows/capsule-cypress.yml @@ -38,6 +38,14 @@ jobs: ## Checkout capsule ####### + # Checkout front ends + - name: Checkout frontend mono repo + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + path: './frontend-monorepo' + # Checkout capsule to build local network - name: Checkout capsule uses: actions/checkout@v2 @@ -87,7 +95,7 @@ jobs: run: vegacapsule nomad & - name: Bootstrap network - run: vegacapsule network bootstrap --config-path=./net_confs/config.hcl --force + run: vegacapsule network bootstrap --config-path=../frontend-monorepo/vegacapsule/config.hcl --force working-directory: capsule ###### @@ -111,23 +119,19 @@ jobs: ## Run some tests ###### - # Checkout front ends - - name: Checkout frontend mono repo - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - name: Derive appropriate SHAs for base and head for `nx affected` commands uses: nrwl/nx-set-shas@v2 with: + working-directory: frontend-monorepo main-branch-name: master - name: Install root dependencies run: yarn install + working-directory: frontend-monorepo - name: Run Cypress tests run: npx nx affected:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome + working-directory: frontend-monorepo env: CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }} CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }} diff --git a/apps/token-e2e/src/fixtures/proposals/asset.json b/apps/token-e2e/src/fixtures/proposals/asset.json new file mode 100644 index 000000000..6ea2d69b3 --- /dev/null +++ b/apps/token-e2e/src/fixtures/proposals/asset.json @@ -0,0 +1,21 @@ +{ + "rationale": { + "title": "Add USDT Coin (USDT)", + "description": "Proposal to add USDT Coin (USDT) as an asset" + }, + "terms": { + "newAsset": { + "changes": { + "name": "USDT Coin", + "symbol": "USDT", + "decimals": "18", + "quantum": "1", + "erc20": { + "contractAddress": "0xb404c51bbc10dcbe948077f18a4b8e553d160084" + } + } + }, + "closingTimestamp": 1662374250, + "enactmentTimestamp": 1662460650 + } +} diff --git a/apps/token-e2e/src/fixtures/proposals/freeform.json b/apps/token-e2e/src/fixtures/proposals/freeform.json index c3eddfe9b..e3726769f 100644 --- a/apps/token-e2e/src/fixtures/proposals/freeform.json +++ b/apps/token-e2e/src/fixtures/proposals/freeform.json @@ -1,11 +1,10 @@ { "rationale": { - "url": "https://dweb.link/ipfs/bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si", - "hash": "bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si", - "description": "Freeform Proposal with unique id of: " + "title": "An example freeform proposal", + "description": "I propose that everyone evaluate the following IPFS document and vote Yes if they agree. [bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si](https://dweb.link/ipfs/bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si)" }, "terms": { "newFreeform": {}, - "closingTimestamp": 1657721401 + "closingTimestamp": 1662374250 } } diff --git a/apps/token-e2e/src/integration/flow/governance-flow.cy.js b/apps/token-e2e/src/integration/flow/governance-flow.cy.js index 7fa17179a..37319f0ac 100644 --- a/apps/token-e2e/src/integration/flow/governance-flow.cy.js +++ b/apps/token-e2e/src/integration/flow/governance-flow.cy.js @@ -1,4 +1,9 @@ /// + +const vegaWalletUnstakedBalance = + '[data-testid="vega-wallet-balance-unstaked"]'; +const vegaWalletStakedBalances = + '[data-testid="vega-wallet-balance-staked-validators"]'; const newProposalButton = '[data-testid="new-proposal-link"]'; const newProposalDatabox = '[data-testid="proposal-data"]'; const newProposalSubmitButton = '[data-testid="proposal-submit"]'; @@ -7,8 +12,22 @@ const viewProposalButton = '[data-testid="view-proposal-btn"]'; const proposalInformationTableRows = '[data-testid="key-value-table-row"]'; const openProposals = '[data-testid="open-proposals"]'; const vegaWalletAssociatedBalance = '[data-testid="currency-value"]'; -const proposalResponseIdPath = 'response.body.data.busEvents.0.event.id'; +const proposalResponseProposalIdPath = + 'response.body.data.busEvents.0.event.id'; +const proposalVoteProgressForPercentage = + '[data-testid="vote-progress-indicator-percentage-for"]'; +const proposalVoteProgressAgainstPercentage = + '[data-testid="vote-progress-indicator-percentage-against"]'; +const proposalVoteProgressForTokens = + '[data-testid="vote-progress-indicator-tokens-for"]'; +const proposalVoteProgressAgainstTokens = + '[data-testid="vote-progress-indicator-tokens-against"]'; +const changeVoteButton = '[data-testid="change-vote-button"]'; +const voteButtons = '[data-testid="vote-buttons"]'; +const rejectProposalsLink = '[href="/governance/rejected"]'; +const feedbackError = '[data-testid="Error"]'; const txTimeout = Cypress.env('txTimeout'); +const epochTimeout = Cypress.env('epochTimeout'); context('Governance flow - with eth and vega wallets connected', function () { before('connect wallets and set approval limit', function () { @@ -19,6 +38,18 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.wrap( network_parameters['governance.proposal.freeform.minProposerBalance'] ).as('minProposerBalance'); + cy.wrap( + network_parameters['governance.proposal.freeform.minVoterBalance'] + ).as('minVoterBalance'); + cy.wrap( + network_parameters['governance.proposal.freeform.requiredMajority'] * + 100 + ).as('requiredMajority'); + cy.wrap( + network_parameters[ + 'governance.proposal.freeform.requiredParticipation' + ] * 100 + ).as('requiredParticipation'); cy.wrap( network_parameters['governance.proposal.freeform.minClose'].split( 'h' @@ -33,11 +64,43 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.vega_wallet_connect(); cy.vega_wallet_set_specified_approval_amount('1000'); cy.reload(); + cy.wait_for_spinner(); cy.verify_page_header('The $VEGA token'); cy.ethereum_wallet_connect(); }); describe('Eth wallet - contains VEGA tokens', function () { + before( + 'checking network parameters (therefore environment) is fit for test', + function () { + assert.isAtLeast( + parseInt(this.minProposerBalance), + 0.00001, + 'Asserting that value is at least 0.00001 for network parameter minProposerBalance' + ); + assert.isAtLeast( + parseInt(this.minVoterBalance), + 0.00001, + 'Asserting that value is at least 0.00001 for network parameter minVoterBalance' + ); + assert.isAtLeast( + parseFloat(this.requiredParticipation), + 0.00001, + 'Asserting that value is at least 0.00001 for network parameter requiredParticipation' + ); + assert.isAtLeast( + parseInt(this.minCloseDays), + 1, + 'Asserting that value is at least 1 for network parameter minCloseDays' + ); + assert.isAtLeast( + parseInt(this.maxCloseDays), + parseInt(this.minCloseDays + 1), + 'Asserting that network parameter maxCloseDays is at least 1 day higher than minCloseDays' + ); + } + ); + beforeEach('visit staking tab', function () { cy.navigate_to('staking'); cy.wait_for_spinner(); @@ -48,24 +111,133 @@ context('Governance flow - with eth and vega wallets connected', function () { }); }); - it('Able to submit a valid freeform proposal - with minimum tokens associated', function () { + it('Able to submit a valid freeform proposal - with minimum required tokens associated', function () { cy.ensure_specified_unstaked_tokens_are_associated( this.minProposerBalance ); cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( (closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); } ); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.contains('Proposal submitted').should('be.visible'); + cy.contains('Confirm transaction in wallet', epochTimeout).should( + 'be.visible' + ); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); cy.get(dialogCloseButton).click(); }); + it('Able to submit a valid freeform proposal - with minimum required tokens associated - but also staked', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.get(vegaWalletUnstakedBalance, txTimeout).should( + 'contain', + this.minProposerBalance, + txTimeout + ); + cy.navigate_to('staking'); + cy.wait_for_spinner(); + cy.click_on_validator_from_list(0); + cy.staking_validator_page_add_stake(this.minProposerBalance); + + cy.get(vegaWalletStakedBalances, txTimeout).should( + 'contain', + this.minProposerBalance, + txTimeout + ); + + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + }); + + it('Newly created freeform proposal - able to filter by proposalID to show it in list', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.wait('@proposalSubmissionCompletion') + .its(proposalResponseProposalIdPath) + .then((proposalId) => { + cy.get('[data-testid="set-proposals-filter-visible"]').click(); + cy.get('[data-testid="filter-input"]').type(proposalId); + cy.get(`#${proposalId}`).should( + 'contain', + `Freeform proposal: ${proposalId}`, + txTimeout + ); + }); + }); + + it('Newly created freeform proposal - able to filter by proposerID to show it in list', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.wait('@proposalSubmissionCompletion').then((proposal) => { + let proposerId = proposal.request.body.variables.partyId; + let proposalId = proposal.response.body.data.busEvents[0].event.id; + cy.get('[data-testid="set-proposals-filter-visible"]').click(); + cy.get('[data-testid="filter-input"]').type(proposerId); + cy.get(`#${proposalId}`).should( + 'contain', + `Freeform proposal: ${proposalId}`, + txTimeout + ); + }); + }); + it('Newly created freeform proposal - shows in an open state', function () { cy.ensure_specified_unstaked_tokens_are_associated( this.minProposerBalance @@ -73,43 +245,41 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days('8').then( (closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); } ); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.contains('Proposal submitted').should('be.visible'); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); cy.wait('@proposalSubmissionCompletion') - .its(proposalResponseIdPath) + .its(proposalResponseProposalIdPath) .then((proposalId) => { - cy.navigate_to('governance'); - cy.wait_for_spinner(); cy.get(openProposals).within(() => { cy.get(`#${proposalId}`) - .should('contain', `Freeform proposal: ${proposalId}`) + .should('contain', `Freeform proposal: ${proposalId}`, txTimeout) .and('contain', 'Open') .and('be.visible') .within(() => { cy.get(viewProposalButton).should('be.visible').click(); }); }); - cy.get(proposalInformationTableRows) - .contains('ID') - .siblings() + cy.get_proposal_information_from_table('ID') .contains(proposalId) - .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('State') - .siblings() - .contains('Open') - .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Type') - .siblings() - .contains('NewFreeform'); + .and('be.visible'); + cy.get_proposal_information_from_table('State') + .contains('STATE_OPEN') + .and('be.visible'); + cy.get_proposal_information_from_table('Type') + .contains('NewFreeform') + .and('be.visible'); }); }); @@ -120,43 +290,39 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days('9').then( (closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.contains('Proposal submitted').should('be.visible'); + + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); cy.navigate_to('governance'); cy.wait_for_spinner(); - cy.get_submitted_proposal().within(() => + cy.get_submitted_proposal_from_proposal_list().within(() => cy.get(viewProposalButton).click() ); cy.convert_unix_timestamp_to_governance_data_table_date_format( closingDateTimestamp ).then((closingDate) => { - cy.get(proposalInformationTableRows) - .contains('Closes on') - .siblings() + cy.get_proposal_information_from_table('Closes on') .contains(closingDate) .should('be.visible'); }); - cy.create_ten_digit_unix_timestamp_for_specified_days('0').then( - (now) => { - cy.convert_unix_timestamp_to_governance_data_table_date_format( - now - ).then((proposalDate) => { - cy.get(proposalInformationTableRows) - .contains('Proposed on') - .siblings() - .contains(proposalDate) - .should('be.visible'); - }); - } - ); - cy.contains('9 days left to vote').should('be.visible'); } ); + cy.get_governance_proposal_date_format_for_specified_days('0').then( + (proposalDate) => { + cy.get_proposal_information_from_table('Proposed on') + .contains(proposalDate) + .should('be.visible'); + } + ); + cy.contains('9 days left to vote').should('be.visible'); }); it('Newly created freeform proposal - shows default status set to fail', function () { @@ -166,41 +332,337 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( (closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); } ); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.contains('Proposal submitted').should('be.visible'); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); cy.navigate_to('governance'); cy.wait_for_spinner(); - cy.get_submitted_proposal().within(() => + cy.get_submitted_proposal_from_proposal_list().within(() => cy.get(viewProposalButton).click() ); cy.contains('Currently set to fail').should('be.visible'); cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should( 'be.visible' ); - cy.get(proposalInformationTableRows) - .contains('Will pass') - .siblings() + cy.get_proposal_information_from_table('Will pass') .contains('👎') .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Majority met') - .siblings() + cy.get_proposal_information_from_table('Majority met') .contains('👎') .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Participation met') - .siblings() + cy.get_proposal_information_from_table('Participation met') .contains('👎') .should('be.visible'); }); + it('Newly created freeform proposal - ability to vote for proposal - with minimum required tokens associated', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list() + .as('submittedProposal') + .within(() => cy.get(viewProposalButton).click()); + cy.vote_for_proposal('for'); + cy.get_governance_proposal_date_format_for_specified_days( + '0', + 'shortMonth' + ).then((votedDate) => { + cy.contains('You voted:') + .siblings() + .contains('For') + .siblings() + .contains(votedDate) + .should('be.visible'); + }); + cy.get(proposalVoteProgressForPercentage) + .contains('100.00%') + .and('be.visible'); + cy.get(proposalVoteProgressAgainstPercentage) + .contains('0.00%') + .and('be.visible'); + cy.get(proposalVoteProgressForTokens).contains('1.00').and('be.visible'); + cy.get(proposalVoteProgressAgainstTokens) + .contains('0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens for proposal') + .should('have.text', parseFloat(this.minProposerBalance).toFixed(2)) + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens against proposal') + .should('have.text', '0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Participation required') + .contains(`${this.requiredParticipation}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Majority Required') + .contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Number of voting parties') + .should('have.text', '1') + .and('be.visible'); + }); + + it('Newly created freeform proposal - ability to vote against proposal - with minimum required tokens associated', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list() + .as('submittedProposal') + .within(() => cy.get(viewProposalButton).click()); + cy.vote_for_proposal('against'); + cy.get_governance_proposal_date_format_for_specified_days( + '0', + 'shortMonth' + ).then((votedDate) => { + cy.contains('You voted:') + .siblings() + .contains('Against') + .siblings() + .contains(votedDate) + .should('be.visible'); + }); + cy.get(proposalVoteProgressForPercentage) + .contains('0.00%') + .and('be.visible'); + cy.get(proposalVoteProgressAgainstPercentage) + .contains('100.00%') + .and('be.visible'); + cy.get(proposalVoteProgressForTokens).contains('0.00').and('be.visible'); + cy.get(proposalVoteProgressAgainstTokens) + .contains('1.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens for proposal') + .should('have.text', '0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens against proposal') + .should('have.text', parseFloat(this.minProposerBalance).toFixed(2)) + .and('be.visible'); + cy.get_proposal_information_from_table('Participation required') + .contains(`${this.requiredParticipation}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Majority Required') + .contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Number of voting parties') + .should('have.text', '1') + .and('be.visible'); + }); + + it('Newly created freeform proposal - ability to change vote from against to for - with minimum required tokens associated', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list() + .as('submittedProposal') + .within(() => cy.get(viewProposalButton).click()); + cy.vote_for_proposal('against'); + cy.get(changeVoteButton).should('be.visible').click(); + cy.wait_for_spinner(); + cy.vote_for_proposal('for'); + cy.get(proposalVoteProgressForPercentage) + .contains('100.00%') + .and('be.visible'); + cy.get(proposalVoteProgressAgainstPercentage) + .contains('0.00%') + .and('be.visible'); + cy.get(proposalVoteProgressForTokens).contains('1.00').and('be.visible'); + cy.get(proposalVoteProgressAgainstTokens) + .contains('0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens for proposal') + .should('have.text', parseFloat(this.minProposerBalance).toFixed(2)) + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens against proposal') + .should('have.text', '0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Participation required') + .contains(`${this.requiredParticipation}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Majority Required') + .contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Number of voting parties') + .should('have.text', '1') + .and('be.visible'); + }); + + it('Newly created freeform proposal - ability to change vote from for to against - with minimum required tokens associated', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list() + .as('submittedProposal') + .within(() => cy.get(viewProposalButton).click()); + cy.vote_for_proposal('for'); + cy.get(changeVoteButton).should('be.visible').click(); + cy.wait_for_spinner(); + cy.vote_for_proposal('against'); + cy.get(proposalVoteProgressForPercentage) + .contains('0.00%') + .and('be.visible'); + cy.get(proposalVoteProgressAgainstPercentage) + .contains('100.00%') + .and('be.visible'); + cy.get(proposalVoteProgressForTokens).contains('0.00').and('be.visible'); + cy.get(proposalVoteProgressAgainstTokens) + .contains('1.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens for proposal') + .should('have.text', '0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens against proposal') + .should('have.text', parseFloat(this.minProposerBalance).toFixed(2)) + .and('be.visible'); + cy.get_proposal_information_from_table('Participation required') + .contains(`${this.requiredParticipation}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Majority Required') + .contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`) + .should('be.visible'); + cy.get_proposal_information_from_table('Number of voting parties') + .should('have.text', '1') + .and('be.visible'); + }); + + it('Newly created freeform proposal - ability to increase associated tokens - so that vote sways result', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days('7').then( + (closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + } + ); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal submitted', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list() + .as('submittedProposal') + .within(() => cy.get(viewProposalButton).click()); + cy.vote_for_proposal('for'); + cy.get_proposal_information_from_table('Total Supply') + .invoke('text') + .then((totalSupply) => { + let tokensRequiredToAcheiveResult = parseFloat( + (totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100 + ).toFixed(2); + cy.ensure_specified_unstaked_tokens_are_associated( + tokensRequiredToAcheiveResult + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get('@submittedProposal').within(() => + cy.get(viewProposalButton).click() + ); + cy.get(proposalVoteProgressForPercentage) + .contains('100.00%') + .and('be.visible'); + cy.get(proposalVoteProgressAgainstPercentage) + .contains('0.00%') + .and('be.visible'); + cy.get(proposalVoteProgressForTokens) + .contains(tokensRequiredToAcheiveResult) + .and('be.visible'); + cy.get(proposalVoteProgressAgainstTokens) + .contains('0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens for proposal') + .should('have.text', tokensRequiredToAcheiveResult) + .and('be.visible'); + cy.get_proposal_information_from_table('Tokens against proposal') + .should('have.text', '0.00') + .and('be.visible'); + cy.get_proposal_information_from_table('Number of voting parties') + .should('have.text', '1') + .and('be.visible'); + }); + }); + it('Creating a proposal - proposal rejected - when closing time sooner than system default', function () { cy.ensure_specified_unstaked_tokens_are_associated( this.minProposerBalance @@ -208,37 +670,25 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days( this.minCloseDays - 1 ).then((closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); }); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.contains('Proposal rejected').should('be.visible'); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal rejected', epochTimeout).should('be.visible'); cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); cy.navigate_to('governance'); cy.wait_for_spinner(); - cy.get_submitted_proposal().within(() => { + cy.get(rejectProposalsLink).click().wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list().within(() => { cy.contains('Rejected').should('be.visible'); cy.contains('Close time too soon').should('be.visible'); - cy.get(viewProposalButton).click(); }); - cy.get(proposalInformationTableRows) - .contains('State') - .siblings() - .contains('Rejected') - .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Rejection reason') - .siblings() - .contains('CloseTimeTooSoon') - .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Error details') - .siblings() - .contains('proposal closing time too soon') - .should('be.visible'); }); it('Creating a proposal - proposal rejected - when closing time later than system default', function () { @@ -248,37 +698,63 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days( this.maxCloseDays + 1 ).then((closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); }); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.contains('Proposal rejected').should('be.visible'); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal rejected', epochTimeout).should('be.visible'); cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); cy.navigate_to('governance'); cy.wait_for_spinner(); - cy.get_submitted_proposal().within(() => { + cy.get(rejectProposalsLink).click().wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list().within(() => { + cy.contains('Rejected').should('be.visible'); + cy.contains('Close time too late').should('be.visible'); + }); + }); + + it('Creating a proposal - proposal rejected - able to access rejected proposals', function () { + cy.ensure_specified_unstaked_tokens_are_associated( + this.minProposerBalance + ); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(newProposalButton).should('be.visible').click(); + cy.create_ten_digit_unix_timestamp_for_specified_days( + this.maxCloseDays + 1 + ).then((closingDateTimestamp) => { + cy.enter_unique_freeform_proposal_body(closingDateTimestamp); + }); + cy.get(newProposalSubmitButton).should('be.visible').click(); + cy.contains('Awaiting network confirmation', epochTimeout).should( + 'be.visible' + ); + cy.contains('Proposal rejected', epochTimeout).should('be.visible'); + cy.get(dialogCloseButton).click(); + cy.wait_for_proposal_sync(); + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get(rejectProposalsLink).click().wait_for_spinner(); + cy.get_submitted_proposal_from_proposal_list().within(() => { cy.contains('Rejected').should('be.visible'); cy.contains('Close time too late').should('be.visible'); cy.get(viewProposalButton).click(); }); - cy.get(proposalInformationTableRows) - .contains('State') - .siblings() - .contains('Rejected') - .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Rejection reason') - .siblings() - .contains('CloseTimeTooLate') - .should('be.visible'); - cy.get(proposalInformationTableRows) - .contains('Error details') - .siblings() + cy.get_proposal_information_from_table('State') + .contains('STATE_REJECTED') + .and('be.visible'); + cy.get_proposal_information_from_table('Rejection reason') + .contains('PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE') + .and('be.visible'); + cy.get_proposal_information_from_table('Error details') .contains('proposal closing time too late') - .should('be.visible'); + .and('be.visible'); }); it('Unable to create a freeform proposal - when no tokens are associated', function () { @@ -290,44 +766,46 @@ context('Governance flow - with eth and vega wallets connected', function () { cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days('1').then( (closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); } ); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.wait('@proposalSubmissionCompletion'); - cy.contains( - 'party has insufficient tokens to submit proposal request in this epoch' - ).should('be.visible'); - cy.get(dialogCloseButton).click(); + + cy.contains('Transaction failed', epochTimeout).should('be.visible'); + cy.get(feedbackError) + .contains( + 'Party has insufficient associated governance tokens in their staking account to submit proposal request' + ) + .should('be.visible'); }); it('Unable to create a freeform proposal - when some but not enough tokens are associated', function () { cy.ensure_specified_unstaked_tokens_are_associated( - this.minProposerBalance - 0.1 + this.minProposerBalance - 0.000001 ); cy.navigate_to('governance'); cy.wait_for_spinner(); cy.get(newProposalButton).should('be.visible').click(); - cy.get(newProposalDatabox).click(); cy.create_ten_digit_unix_timestamp_for_specified_days('1').then( (closingDateTimestamp) => { cy.enter_unique_freeform_proposal_body(closingDateTimestamp); } ); cy.get(newProposalSubmitButton).should('be.visible').click(); - cy.wait('@proposalSubmissionCompletion'); - cy.contains( - 'party has insufficient tokens to submit proposal request in this epoch' - ).should('be.visible'); - cy.get(dialogCloseButton).click(); + + cy.contains('Transaction failed', epochTimeout).should('be.visible'); + cy.get(feedbackError) + .contains( + 'Party has insufficient associated governance tokens in their staking account to submit proposal request' + ) + .should('be.visible'); }); Cypress.Commands.add( 'convert_unix_timestamp_to_governance_data_table_date_format', - (unixTimestamp) => { + (unixTimestamp, monthTextLength = 'longMonth') => { let dateSupplied = new Date(unixTimestamp * 1000), year = dateSupplied.getFullYear(), months = [ @@ -345,9 +823,11 @@ context('Governance flow - with eth and vega wallets connected', function () { 'December', ], month = months[dateSupplied.getMonth()], + shortMonth = months[dateSupplied.getMonth()].substring(0, 3), date = dateSupplied.getDate(); - return `${date} ${month} ${year}`; + if (monthTextLength === 'longMonth') return `${date} ${month} ${year}`; + else return `${date} ${shortMonth} ${year}`; } ); @@ -365,7 +845,7 @@ context('Governance flow - with eth and vega wallets connected', function () { Cypress.Commands.add('enter_unique_freeform_proposal_body', (timestamp) => { cy.fixture('/proposals/freeform.json').then((freeformProposal) => { freeformProposal.terms.closingTimestamp = timestamp; - freeformProposal.rationale.description += timestamp; + freeformProposal.rationale.title += timestamp; let proposalPayload = JSON.stringify(freeformProposal); cy.get(newProposalDatabox).type(proposalPayload, { @@ -385,7 +865,7 @@ context('Governance flow - with eth and vega wallets connected', function () { }, headers: { 'content-type': 'application/json' }, }) - .its('body.data.networkParameters') + .its(`body.data.networkParameters`) .then(function (response) { let object = response.reduce(function (r, e) { r[e.key] = e.value; @@ -396,12 +876,74 @@ context('Governance flow - with eth and vega wallets connected', function () { }); }); - Cypress.Commands.add('get_submitted_proposal', () => { + Cypress.Commands.add('get_submitted_proposal_from_proposal_list', () => { cy.wait('@proposalSubmissionCompletion') - .its(proposalResponseIdPath) + .its(proposalResponseProposalIdPath) .then((proposalId) => { - cy.get(`#${proposalId}`); + return cy.get(`#${proposalId}`); }); }); + + Cypress.Commands.add( + 'get_governance_proposal_date_format_for_specified_days', + (days, shortOrLong) => { + cy.create_ten_digit_unix_timestamp_for_specified_days(days).then( + (date) => { + cy.convert_unix_timestamp_to_governance_data_table_date_format( + date, + shortOrLong + ).then((convertedDate) => { + return convertedDate; + }); + } + ); + } + ); + + Cypress.Commands.add('get_proposal_information_from_table', (heading) => { + cy.get(proposalInformationTableRows).contains(heading).siblings(); + }); + + Cypress.Commands.add('vote_for_proposal', (vote) => { + cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 }); + cy.get(voteButtons).contains(vote).click(); + cy.contains('Casting vote...').should('be.visible'); + cy.contains('Casting vote...', txTimeout).should('not.exist'); + + // below section temporary until #1090 fixed Casting vote in vegacapsule always says: + // Something went wrong, and your vote was not seen by the network - despite vote success + cy.navigate_to('governance'); + cy.wait_for_spinner(); + cy.get('@submittedProposal').within(() => + cy.get(viewProposalButton).click() + ); + cy.wait_for_spinner(); + }); + + Cypress.Commands.add('wait_for_proposal_sync', () => { + // This is a workaround function required because after posting a proposal + // and waiting for the ProposalEvent network call to respond there can still be a few seconds + // before proposal appears in the list - so rather than hard coded wait - we just wait on the + // delegation checks that are performed on the governance page. + + cy.intercept('POST', '/query', (req) => { + if (req.body.operationName === 'Delegations') { + req.alias = 'proposalDelegationsCompletion'; + } + }); + + // waiting for two network calls + cy.wait([ + '@proposalDelegationsCompletion', + '@proposalDelegationsCompletion', + ]); + + // Turn off this intercept from here on in + cy.intercept('POST', '/query', (req) => { + if (req.body.operationName === 'Delegations') { + req.continue(); + } + }); + }); }); }); diff --git a/apps/token-e2e/src/integration/flow/staking-flow.cy.js b/apps/token-e2e/src/integration/flow/staking-flow.cy.js index d6d42e372..3f3a4d938 100644 --- a/apps/token-e2e/src/integration/flow/staking-flow.cy.js +++ b/apps/token-e2e/src/integration/flow/staking-flow.cy.js @@ -55,7 +55,6 @@ context('Staking Tab - with eth and vega wallets connected', function () { } ); - // 1002-STKE-004 it('Able to stake against a validator - using vega from wallet', function () { cy.staking_page_associate_tokens('3'); @@ -670,6 +669,7 @@ context('Staking Tab - with eth and vega wallets connected', function () { }); it('Associating wallet tokens - when some already staked - auto stakes tokens to staked validator', function () { + // 1002-STKE-004 cy.staking_page_associate_tokens('3'); cy.get(vegaWalletUnstakedBalance, txTimeout).should( @@ -705,6 +705,7 @@ context('Staking Tab - with eth and vega wallets connected', function () { }); it('Associating vesting contract tokens - when some already staked - auto stakes tokens to staked validator', function () { + // 1002-STKE-004 cy.staking_page_associate_tokens('3', { type: 'contract' }); cy.get(vegaWalletUnstakedBalance, txTimeout).should( @@ -740,6 +741,7 @@ context('Staking Tab - with eth and vega wallets connected', function () { }); it('Associating vesting contract tokens - when wallet tokens already staked - auto stakes tokens to staked validator', function () { + // 1002-STKE-004 cy.staking_page_associate_tokens('3', { type: 'wallet' }); cy.get(vegaWalletUnstakedBalance, txTimeout).should( @@ -775,6 +777,7 @@ context('Staking Tab - with eth and vega wallets connected', function () { }); it('Associating tokens - with multiple validators already staked - auto stakes to staked validators - abiding by existing stake ratio', function () { + // 1002-STKE-004 cy.staking_page_associate_tokens('6'); cy.get(vegaWalletUnstakedBalance, txTimeout).should( diff --git a/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx b/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx index 7860d7763..fec5dfe8f 100644 --- a/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx +++ b/apps/token/src/routes/governance/components/vote-details/vote-buttons.tsx @@ -145,6 +145,7 @@ export const VoteButtons = ({ ) : null} {proposalState === ProposalState.STATE_OPEN ? ( { setChangeVote(true); }} @@ -161,7 +162,7 @@ export const VoteButtons = ({ } return ( -
+