From 2c3c685522654f8fd3003060dcb86c55bc98ac9b Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Tue, 17 Jan 2023 09:33:43 -0600 Subject: [PATCH] feat(#2255): capsule tests for trading (#2498) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: improve proposal types, add faucet for trading asset * chore: yarn lock update * fix: capsule tests to avoid jsx import * chore: throw error so that ethereum setup failure is caught * chore: org files and add logging * chore: revert current trading-e2e .env file * chore: add .env.capsule for trading tests on capsule * chore: remove unused nx command * chore: remove try catchs and allow errors to be caught by cypress * chore: add slow tag for capsule test * feat: move create market setup to libs/cypress * chore: clearly separate vars meant for capsule with vars meant for non-capsule * feat: remove hardcoded urls and use config obj * chore: fix missing comma in genesis.tmpl * chore: added limit order edit price test * chore: remove encoding of transaction, update proposal tx trigger ratio to string * chore: fix date and add date verification for update order * chore: formatting fix * chore: adjust trading app tests and workflows to be run in capsule in ci (#2574) * chore: adjust trading app tests and workflows to be run in capsule in ci * chore: fix token tests * chore: increase timeout as it fails in ci * chore: move market check to before function * chore: add capsule logs to trading * chore: increase proposal close time, also break up script for better debugging * chore: bump up ethereum account setup timeout * chore: suppress fetch logs to help with debugging * chore: revert cy wraps now with suppressed logging * chore: decrease tick amount * chore: promise with timeout for better debugging * chore: bump individual timeouts * chore: bump timeout for tx wait promises * chore: minor fix * fix: leave Promise * fix: leave Promise * chore: fix home fail Co-authored-by: Joe Co-authored-by: Dariusz Majcherczyk Co-authored-by: Radosław Szpiech Co-authored-by: Madalina Raicu --- .../setup-vegawallet-docker/action.yml | 3 - .../workflows/cypress-console-lite-e2e.yml | 12 +- .github/workflows/cypress-explorer-e2e.yml | 15 +- .github/workflows/cypress-token-e2e.yml | 15 +- .github/workflows/cypress-trading-e2e.yml | 28 ++- apps/console-lite-e2e/.env | 13 +- apps/console-lite-e2e/.env.capsule | 11 + apps/console-lite-e2e/.env.stagnet3 | 11 + apps/console-lite-e2e/cypress.config.js | 16 -- apps/explorer-e2e/.env | 1 + apps/token-e2e/.env | 1 + apps/trading-e2e/.env | 26 ++- apps/trading-e2e/.env.capsule | 26 +++ apps/trading-e2e/.env.stagnet3 | 22 ++ apps/trading-e2e/cypress.config.js | 7 +- .../trading-e2e/src/integration/capsule.cy.ts | 152 ++++++++++++++ apps/trading-e2e/src/integration/global.cy.ts | 4 +- apps/trading-e2e/src/integration/home.cy.ts | 4 +- .../src/integration/market-summary.cy.ts | 2 +- .../trading-e2e/src/integration/markets.cy.ts | 2 +- .../src/integration/trading-deal-ticket.cy.ts | 20 +- .../src/integration/trading-orders.cy.ts | 1 + .../src/integration/withdraw.cy.ts | 5 +- apps/trading-e2e/src/support/create-order.ts | 26 +++ apps/trading-e2e/src/support/index.js | 1 + .../src/support/order-validation.ts | 2 +- apps/trading/.env.capsule | 11 + libs/cypress/src/index.ts | 3 + libs/cypress/src/lib/capsule/create-market.ts | 137 +++++++++++++ .../cypress/src/lib/capsule/ethereum-setup.ts | 191 +++++++++++++++++ libs/cypress/src/lib/capsule/faucet-asset.ts | 23 +++ libs/cypress/src/lib/capsule/logging.ts | 5 + .../cypress/src/lib/capsule/propose-market.ts | 192 ++++++++++++++++++ libs/cypress/src/lib/capsule/request.ts | 39 ++++ libs/cypress/src/lib/capsule/vote.ts | 33 +++ .../cypress/src/lib/commands/create-market.ts | 50 +++++ libs/cypress/src/lib/mock-gql.ts | 2 +- libs/cypress/src/lib/utils.ts | 20 ++ .../markets-container/market-list-table.tsx | 10 + .../lib/components/order-list/order-list.tsx | 23 ++- package.json | 1 + vegacapsule/genesis.tmpl | 1 + 42 files changed, 1060 insertions(+), 107 deletions(-) create mode 100644 apps/console-lite-e2e/.env.capsule create mode 100644 apps/console-lite-e2e/.env.stagnet3 create mode 100644 apps/trading-e2e/.env.capsule create mode 100644 apps/trading-e2e/.env.stagnet3 create mode 100644 apps/trading-e2e/src/integration/capsule.cy.ts create mode 100644 apps/trading-e2e/src/support/create-order.ts create mode 100644 apps/trading/.env.capsule create mode 100644 libs/cypress/src/lib/capsule/create-market.ts create mode 100644 libs/cypress/src/lib/capsule/ethereum-setup.ts create mode 100644 libs/cypress/src/lib/capsule/faucet-asset.ts create mode 100644 libs/cypress/src/lib/capsule/logging.ts create mode 100644 libs/cypress/src/lib/capsule/propose-market.ts create mode 100644 libs/cypress/src/lib/capsule/request.ts create mode 100644 libs/cypress/src/lib/capsule/vote.ts create mode 100644 libs/cypress/src/lib/commands/create-market.ts create mode 100644 libs/cypress/src/lib/utils.ts diff --git a/.github/actions/setup-vegawallet-docker/action.yml b/.github/actions/setup-vegawallet-docker/action.yml index d596288d6..10c67568f 100644 --- a/.github/actions/setup-vegawallet-docker/action.yml +++ b/.github/actions/setup-vegawallet-docker/action.yml @@ -1,6 +1,3 @@ -inputs: - passphrase: - description: 'Wallet password' outputs: token: description: 'api-token of wallet' diff --git a/.github/workflows/cypress-console-lite-e2e.yml b/.github/workflows/cypress-console-lite-e2e.yml index 484ec19d5..502c1463e 100644 --- a/.github/workflows/cypress-console-lite-e2e.yml +++ b/.github/workflows/cypress-console-lite-e2e.yml @@ -42,17 +42,16 @@ jobs: - name: Install root dependencies run: yarn install --frozen-lockfile working-directory: frontend-monorepo - ###### ## Setup a Vega wallet for our user ###### - - name: Set up Vegawallet + - name: Run Vegacapsule network + uses: ./frontend-monorepo/.github/actions/run-vegacapsule + + - name: Set up Vegawallet for capsule id: setup-vega - uses: ./frontend-monorepo/.github/actions/setup-vegawallet - with: - recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }} - passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }} + uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker # To make sure that all Cypress binaries are installed properly - name: Install cypress bins @@ -63,7 +62,6 @@ jobs: run: npx nx run console-lite-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }} 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 }} CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }} CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }} diff --git a/.github/workflows/cypress-explorer-e2e.yml b/.github/workflows/cypress-explorer-e2e.yml index 43633f7a0..885d60e44 100644 --- a/.github/workflows/cypress-explorer-e2e.yml +++ b/.github/workflows/cypress-explorer-e2e.yml @@ -51,22 +51,16 @@ jobs: run: yarn install --frozen-lockfile working-directory: frontend-monorepo - ####### - ## Build and run Vegacapsule network - ####### - - - name: Build and run Vegacapsule network - uses: ./frontend-monorepo/.github/actions/run-vegacapsule - ###### ## Setup a Vega wallet for our user ###### - - name: Set up Vegawallet for docker + - name: Run Vegacapsule network + uses: ./frontend-monorepo/.github/actions/run-vegacapsule + + - name: Set up Vegawallet for capsule id: setup-vega uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker - with: - passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }} ###### ## Run some tests @@ -81,7 +75,6 @@ jobs: run: npx nx run explorer-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }} 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 }} CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }} CYPRESS_NIGHTLY_RUN: ${{ inputs.night-run }} diff --git a/.github/workflows/cypress-token-e2e.yml b/.github/workflows/cypress-token-e2e.yml index 098d7d091..eae474ff7 100644 --- a/.github/workflows/cypress-token-e2e.yml +++ b/.github/workflows/cypress-token-e2e.yml @@ -47,22 +47,16 @@ jobs: run: yarn install --frozen-lockfile working-directory: frontend-monorepo - ####### - ## Build and run Vegacapsule network - ####### - - - name: Build and run Vegacapsule network - uses: ./frontend-monorepo/.github/actions/run-vegacapsule - ###### ## Setup a Vega wallet for our user ###### - - name: Set up Vegawallet for docker + - name: Run Vegacapsule network + uses: ./frontend-monorepo/.github/actions/run-vegacapsule + + - name: Set up Vegawallet for capsule id: setup-vega uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker - with: - passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }} ###### ## Run some tests @@ -77,7 +71,6 @@ jobs: run: npx nx run token-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }} 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 }} CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }} CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }} diff --git a/.github/workflows/cypress-trading-e2e.yml b/.github/workflows/cypress-trading-e2e.yml index c526af488..8ea9ae615 100644 --- a/.github/workflows/cypress-trading-e2e.yml +++ b/.github/workflows/cypress-trading-e2e.yml @@ -48,12 +48,12 @@ jobs: ## Setup a Vega wallet for our user ###### - - name: Set up Vegawallet + - name: Run Vegacapsule network + uses: ./frontend-monorepo/.github/actions/run-vegacapsule + + - name: Set up Vegawallet for capsule id: setup-vega - uses: ./frontend-monorepo/.github/actions/setup-vegawallet - with: - recovery: ${{ secrets.TRADING_TEST_VEGA_WALLET_RECOVERY }} - passphrase: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }} + uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker # To make sure that all Cypress binaries are installed properly - name: Install cypress bins @@ -64,7 +64,23 @@ jobs: run: npx nx run trading-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }} 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 }} CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }} CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }} + + ###### + ## Upload logs + ###### + + - name: Rename files to allow archive + if: ${{ always() }} + run: | + while read -r file; do + mv "${file}" "$(echo ${file} | sed 's|:|-|g')" + done< <(find /home/runner/.vegacapsule/testnet/logs -type f) + + - uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: logs + path: /home/runner/.vegacapsule/testnet/logs diff --git a/apps/console-lite-e2e/.env b/apps/console-lite-e2e/.env index e99c2a8c3..699a0e99d 100644 --- a/apps/console-lite-e2e/.env +++ b/apps/console-lite-e2e/.env @@ -1,10 +1,11 @@ -# App configuration variables -NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json -NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql -NX_VEGA_ENV=STAGNET3 -NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 +NX_ETHEREUM_PROVIDER_URL=http://localhost:8545 NX_ETHERSCAN_URL=https://sepolia.etherscan.io +NX_VEGA_CONFIG_URL='' +NX_VEGA_ENV=CUSTOM NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +NX_VEGA_URL=http://localhost:3028/query NX_VEGA_WALLET_URL=http://localhost:1789 -CYPRESS_VEGA_ENV=STAGNET3 + +CYPRESS_VEGA_ENV=CUSTOM +CYPRESS_VEGA_URL=http://localhost:3028/query CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/console-lite-e2e/.env.capsule b/apps/console-lite-e2e/.env.capsule new file mode 100644 index 000000000..699a0e99d --- /dev/null +++ b/apps/console-lite-e2e/.env.capsule @@ -0,0 +1,11 @@ +NX_ETHEREUM_PROVIDER_URL=http://localhost:8545 +NX_ETHERSCAN_URL=https://sepolia.etherscan.io +NX_VEGA_CONFIG_URL='' +NX_VEGA_ENV=CUSTOM +NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +NX_VEGA_URL=http://localhost:3028/query +NX_VEGA_WALLET_URL=http://localhost:1789 + +CYPRESS_VEGA_ENV=CUSTOM +CYPRESS_VEGA_URL=http://localhost:3028/query +CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/console-lite-e2e/.env.stagnet3 b/apps/console-lite-e2e/.env.stagnet3 new file mode 100644 index 000000000..5086c4e28 --- /dev/null +++ b/apps/console-lite-e2e/.env.stagnet3 @@ -0,0 +1,11 @@ +NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 +NX_ETHERSCAN_URL=https://sepolia.etherscan.io +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json +NX_VEGA_ENV=STAGNET3 +NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql +NX_VEGA_WALLET_URL=http://localhost:1789 + +CYPRESS_VEGA_ENV=STAGNET3 +CYPRESS_VEGA_URL=https://api.stagnet3.vega.xyz/graphql +CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/console-lite-e2e/cypress.config.js b/apps/console-lite-e2e/cypress.config.js index b1d8da6a5..070c2893a 100644 --- a/apps/console-lite-e2e/cypress.config.js +++ b/apps/console-lite-e2e/cypress.config.js @@ -1,7 +1,6 @@ const { defineConfig } = require('cypress'); module.exports = defineConfig({ projectId: 'et4snf', - e2e: { setupNodeEvents(on, config) { require('@cypress/grep/src/plugin')(config); @@ -23,21 +22,6 @@ module.exports = defineConfig({ viewportHeight: 900, }, env: { - ETHEREUM_PROVIDER_URL: - 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8', - VEGA_PUBLIC_KEY: - '47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278', - VEGA_PUBLIC_KEY2: - '1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4', - TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278', - TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4', - ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158', - ETHERSCAN_URL: 'https://sepolia.etherscan.io', - tsConfig: 'tsconfig.json', - TAGS: 'not @todo and not @ignore and not @manual', - TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123', - ETH_WALLET_MNEMONIC: - 'ugly gallery notice network true range brave clarify flat logic someone chunk', grepTags: '@regression @smoke @slow', grepFilterSpecs: true, grepOmitFiltered: true, diff --git a/apps/explorer-e2e/.env b/apps/explorer-e2e/.env index 7971c46e4..b48aad733 100644 --- a/apps/explorer-e2e/.env +++ b/apps/explorer-e2e/.env @@ -18,3 +18,4 @@ NX_EXPLORER_PARTIES=1 NX_EXPLORER_VALIDATORS=1 CYPRESS_VEGA_WALLET_API_TOKEN= +CYPRESS_VEGA_URL=http://localhost:3028/query diff --git a/apps/token-e2e/.env b/apps/token-e2e/.env index a10606598..f6b298526 100644 --- a/apps/token-e2e/.env +++ b/apps/token-e2e/.env @@ -15,4 +15,5 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet #Test configuration variables CYPRESS_FAIRGROUND=false +CYPRESS_VEGA_URL=http://localhost:3028/query CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/trading-e2e/.env b/apps/trading-e2e/.env index 69bb96cdb..d6f7eb7d7 100644 --- a/apps/trading-e2e/.env +++ b/apps/trading-e2e/.env @@ -1,16 +1,26 @@ -NX_VEGA_WALLET_URL=http://localhost:1789 -NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 +NX_ETHEREUM_PROVIDER_URL=http://localhost:8545 NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz -NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json -NX_VEGA_ENV=STAGNET3 +NX_VEGA_CONFIG_URL='' +NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet +NX_VEGA_ENV=CUSTOM NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"} NX_VEGA_TOKEN_URL=https://token.fairground.wtf -NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql +NX_VEGA_URL=http://localhost:3028/query NX_VEGA_WALLET_URL=http://localhost:1789 -NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet -CYPRESS_VEGA_URL=https://api.stagnet3.vega.xyz/graphql + +# Expose some env vars to cypress environment for market setup +CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session +CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545 CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz -CYPRESS_VEGA_ENV=STAGNET3 +CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint +CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65 +CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535 +CYPRESS_VEGA_ENV=CUSTOM +CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65 +CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535 +CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf +CYPRESS_VEGA_URL=http://localhost:3028/query +CYPRESS_VEGA_WALLET_URL=http://localhost:1789 CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/trading-e2e/.env.capsule b/apps/trading-e2e/.env.capsule new file mode 100644 index 000000000..d6f7eb7d7 --- /dev/null +++ b/apps/trading-e2e/.env.capsule @@ -0,0 +1,26 @@ +NX_ETHEREUM_PROVIDER_URL=http://localhost:8545 +NX_ETHERSCAN_URL=https://sepolia.etherscan.io +NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz +NX_VEGA_CONFIG_URL='' +NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet +NX_VEGA_ENV=CUSTOM +NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"} +NX_VEGA_TOKEN_URL=https://token.fairground.wtf +NX_VEGA_URL=http://localhost:3028/query +NX_VEGA_WALLET_URL=http://localhost:1789 + +# Expose some env vars to cypress environment for market setup +CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session +CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545 +CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint +CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65 +CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535 +CYPRESS_VEGA_ENV=CUSTOM +CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65 +CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535 +CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf +CYPRESS_VEGA_URL=http://localhost:3028/query +CYPRESS_VEGA_WALLET_URL=http://localhost:1789 +CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/trading-e2e/.env.stagnet3 b/apps/trading-e2e/.env.stagnet3 new file mode 100644 index 000000000..268959384 --- /dev/null +++ b/apps/trading-e2e/.env.stagnet3 @@ -0,0 +1,22 @@ +NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 +NX_ETHERSCAN_URL=https://sepolia.etherscan.io +NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz +NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json +NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet +NX_VEGA_ENV=STAGNET3 +NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"} +NX_VEGA_TOKEN_URL=https://stagnet3.token.vega.xyz +NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql +NX_VEGA_WALLET_URL=http://localhost:1789 + +CYPRESS_ETH_WALLET_MNEMONIC=ugly gallery notice network true range brave clarify flat logic someone chunk +CYPRESS_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=1a18cd…0cf2e4 +CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=47836c…c7d278 +CYPRESS_VEGA_ENV=STAGNET3 +CYPRESS_VEGA_PUBLIC_KEY=1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4 +CYPRESS_VEGA_PUBLIC_KEY2=47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278 +CYPRESS_VEGA_TOKEN_URL=https://stagnet3.token.vega.xyz +CYPRESS_VEGA_URL=https://api.stagnet3.vega.xyz/graphql +CYPRESS_VEGA_WALLET_API_TOKEN= diff --git a/apps/trading-e2e/cypress.config.js b/apps/trading-e2e/cypress.config.js index b44229528..c8388f4d0 100644 --- a/apps/trading-e2e/cypress.config.js +++ b/apps/trading-e2e/cypress.config.js @@ -24,20 +24,17 @@ module.exports = defineConfig({ requestTimeout: 20000, }, env: { - ETHEREUM_PROVIDER_URL: - 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8', VEGA_PUBLIC_KEY: '47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278', VEGA_PUBLIC_KEY2: '1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4', TRUNCATED_VEGA_PUBLIC_KEY: '47836c…c7d278', TRUNCATED_VEGA_PUBLIC_KEY2: '1a18cd…0cf2e4', + ETHEREUM_PROVIDER_URL: + 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8', ETHEREUM_WALLET_ADDRESS: '0x265Cc6d39a1B53d0d92068443009eE7410807158', ETHERSCAN_URL: 'https://sepolia.etherscan.io', ETHEREUM_CHAIN_ID: 11155111, - tsConfig: 'tsconfig.json', - TAGS: 'not @todo and not @ignore and not @manual', - TRADING_TEST_VEGA_WALLET_PASSPHRASE: '123', ETH_WALLET_MNEMONIC: 'ugly gallery notice network true range brave clarify flat logic someone chunk', TRADING_MODE_LINK: diff --git a/apps/trading-e2e/src/integration/capsule.cy.ts b/apps/trading-e2e/src/integration/capsule.cy.ts new file mode 100644 index 000000000..c6cf0025d --- /dev/null +++ b/apps/trading-e2e/src/integration/capsule.cy.ts @@ -0,0 +1,152 @@ +import { removeDecimal } from '@vegaprotocol/cypress'; +import * as Schema from '@vegaprotocol/types'; +import { + OrderStatusMapping, + OrderTimeInForceMapping, + OrderTypeMapping, + Side, +} from '@vegaprotocol/types'; +import { isBefore, isAfter, addSeconds, subSeconds } from 'date-fns'; +import { createOrder } from '../support/create-order'; + +const orderSize = 'size'; +const orderType = 'type'; +const orderStatus = 'status'; +const orderRemaining = 'remaining'; +const orderPrice = 'price'; +const orderTimeInForce = 'timeInForce'; +const orderCreatedAt = 'createdAt'; +const orderUpdatedAt = 'updatedAt'; + +// TODO: ensure this test runs only if capsule is running via workflow +describe('capsule', { tags: '@slow' }, () => { + before(() => { + cy.createMarket(); + cy.get('@markets').then((markets) => { + cy.wrap(markets[0]).as('market'); + }); + }); + + beforeEach(() => { + cy.setVegaWallet(); + }); + + it('can place and receive an order', function () { + const market = this.market; + cy.visit(`/#/markets/${market.id}`); + const order = { + marketId: market.id, + type: Schema.OrderType.TYPE_LIMIT, + side: Schema.Side.SIDE_BUY, + size: '0.0005', + price: '390', + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, + }; + const rawPrice = removeDecimal(order.price, market.decimalPlaces); + const rawSize = removeDecimal(order.size, market.positionDecimalPlaces); + + createOrder(order); + + cy.getByTestId('dialog-title').should( + 'contain.text', + 'Awaiting network confirmation' + ); + cy.getByTestId('dialog-title').should('contain.text', 'Order submitted'); + cy.getByTestId('dialog-close').click(); + + // orderbook cells are keyed by price level + cy.getByTestId('tab-orderbook') + .get(`[data-testid="price-${rawPrice}"]`) + .should('contain.text', order.price) + .get(`[data-testid="bid-vol-${rawPrice}"]`) + .should('contain.text', rawSize); + + cy.getByTestId('Orders').click(); + cy.getByTestId('tab-orders').within(() => { + cy.get('.ag-center-cols-container') + .children() + .first() + .within(() => { + cy.get(`[col-id='${orderSize}']`).should( + 'contain.text', + order.side === Side.SIDE_BUY ? '+' : '-' + order.size + ); + + cy.get(`[col-id='${orderType}']`).should( + 'contain.text', + OrderTypeMapping[order.type] + ); + + cy.get(`[col-id='${orderStatus}']`).should( + 'contain.text', + OrderStatusMapping.STATUS_ACTIVE + ); + + cy.get(`[col-id='${orderRemaining}']`).should( + 'contain.text', + `0.00/${order.size}` + ); + + cy.get(`[col-id='${orderPrice}']`).then(($price) => { + expect(parseFloat($price.text())).to.equal(parseFloat(order.price)); + }); + + cy.get(`[col-id='${orderTimeInForce}']`).should( + 'contain.text', + OrderTimeInForceMapping[order.timeInForce] + ); + + checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderCreatedAt); + }); + }); + + cy.getByTestId('edit').first().click(); + cy.getByTestId('dialog-title').should('contain.text', 'Edit order'); + cy.get('#limitPrice').focus().clear().type('200'); + cy.getByTestId('edit-order').find('[type="submit"]').click(); + cy.getByTestId('dialog-title').should('contain.text', 'Order updated'); + cy.getByTestId('dialog-close').click(); + + cy.get('.ag-center-cols-container') + .children() + .first() + .within(() => { + cy.get(`[col-id='${orderPrice}']`).then(($price) => { + expect(parseFloat($price.text())).to.equal(parseFloat('200')); + }); + checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt); + }); + + cy.getByTestId('cancel').first().click(); + + cy.getByTestId('dialog-title').should( + 'contain.text', + 'Awaiting network confirmation' + ); + cy.getByTestId('dialog-title').should('contain.text', 'Order cancelled'); + cy.getByTestId('dialog-close').click(); + + cy.getByTestId('tab-orders') + .get('.ag-center-cols-container') + .children() + .first() + .get(`[col-id='${orderStatus}']`) + .should('contain.text', OrderStatusMapping.STATUS_CANCELLED); + }); +}); +function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) { + cy.get(`[col-id='${date}']`) + .children('span') + .invoke('data', 'value') + .then(($dateTime) => { + // allow a date 5 seconds either side to allow for + // unexpected latency + const minBefore = subSeconds(new Date(), 5); + const maxAfter = addSeconds(new Date(), 5); + console.log(maxAfter); + const date = new Date($dateTime.toString()); + expect(isAfter(date, minBefore) && isBefore(date, maxAfter)).to.equal( + true + ); + }); +} diff --git a/apps/trading-e2e/src/integration/global.cy.ts b/apps/trading-e2e/src/integration/global.cy.ts index 43da58adc..acce4762f 100644 --- a/apps/trading-e2e/src/integration/global.cy.ts +++ b/apps/trading-e2e/src/integration/global.cy.ts @@ -96,11 +96,11 @@ describe('ethereum wallet', { tags: '@smoke' }, () => { beforeEach(() => { cy.mockWeb3Provider(); // Using portfolio withdrawals tab is it requires Ethereum wallet connection - cy.visit('/#/portfolio'); cy.mockTradingPage(); cy.mockSubscription(); + cy.setVegaWallet(); + cy.visit('/#/portfolio'); cy.get('main[data-testid="/portfolio"]').should('exist'); - cy.connectVegaWallet(); cy.getByTestId('Withdrawals').click(); }); diff --git a/apps/trading-e2e/src/integration/home.cy.ts b/apps/trading-e2e/src/integration/home.cy.ts index 6f082ea2b..7c6682a64 100644 --- a/apps/trading-e2e/src/integration/home.cy.ts +++ b/apps/trading-e2e/src/integration/home.cy.ts @@ -136,7 +136,7 @@ describe('home', { tags: '@regression' }, () => { }); describe('no markets found', () => { - it('redirects to a the empty market page and displays welcome notice', () => { + beforeEach(() => { cy.mockGQL((req) => { const data = { marketsConnection: { @@ -159,6 +159,8 @@ describe('home', { tags: '@regression' }, () => { cy.visit('/'); cy.wait('@Markets'); cy.wait('@MarketsData'); + }); + it('redirects to a the empty market page and displays welcome notice', () => { cy.url().should('eq', Cypress.config().baseUrl + `/#/markets`); cy.getByTestId('welcome-notice-title').should( 'contain.text', diff --git a/apps/trading-e2e/src/integration/market-summary.cy.ts b/apps/trading-e2e/src/integration/market-summary.cy.ts index 34845a914..6454f218d 100644 --- a/apps/trading-e2e/src/integration/market-summary.cy.ts +++ b/apps/trading-e2e/src/integration/market-summary.cy.ts @@ -49,7 +49,7 @@ describe('Market proposal notification', { tags: '@smoke' }, () => { cy.getByTestId('external-link').should( 'have.attr', 'href', - 'https://stagnet3.token.vega.xyz/proposals/123' + `${Cypress.env('VEGA_TOKEN_URL')}/proposals/123` ); }); }); diff --git a/apps/trading-e2e/src/integration/markets.cy.ts b/apps/trading-e2e/src/integration/markets.cy.ts index c36c9b6aa..1388df600 100644 --- a/apps/trading-e2e/src/integration/markets.cy.ts +++ b/apps/trading-e2e/src/integration/markets.cy.ts @@ -108,7 +108,7 @@ describe('markets table', { tags: '@smoke' }, () => { .and( 'have.attr', 'href', - 'https://stagnet3.token.vega.xyz/proposals/propose/new-market' + `${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market` ); }); }); diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts index 853c358a8..a3a3fb6f0 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts @@ -3,6 +3,7 @@ import { aliasGQLQuery, mockConnectWallet } from '@vegaprotocol/cypress'; import { testOrderSubmission } from '../support/order-validation'; import type { OrderSubmission } from '@vegaprotocol/wallet'; import { accountsQuery, estimateOrderQuery } from '@vegaprotocol/mock'; +import { createOrder } from '../support/create-order'; const orderSizeField = 'order-size'; const orderPriceField = 'order-price'; @@ -717,22 +718,3 @@ describe('account validation', { tags: '@regression' }, () => { }); }); }); - -const createOrder = (order: OrderSubmission): void => { - const { type, side, size, price, timeInForce, expiresAt } = order; - - cy.getByTestId(`order-type-${type}`).click(); - cy.getByTestId(`order-side-${side}`).click(); - cy.getByTestId(orderSizeField).clear().type(size); - if (price) { - cy.getByTestId(orderPriceField).clear().type(price); - } - cy.getByTestId(orderTIFDropDown).select(timeInForce); - if (timeInForce === 'TIME_IN_FORCE_GTT') { - if (!expiresAt) { - throw new Error('Specify expiresAt if using GTT'); - } - cy.getByTestId('date-picker-field').type(expiresAt); - } - cy.getByTestId(placeOrderBtn).click(); -}; diff --git a/apps/trading-e2e/src/integration/trading-orders.cy.ts b/apps/trading-e2e/src/integration/trading-orders.cy.ts index 834510ea0..af5bbff23 100644 --- a/apps/trading-e2e/src/integration/trading-orders.cy.ts +++ b/apps/trading-e2e/src/integration/trading-orders.cy.ts @@ -35,6 +35,7 @@ describe('orders list', { tags: '@smoke' }, () => { }); cy.wait('@Markets'); }); + it('renders orders', () => { cy.getByTestId('tab-orders').should('be.visible'); cy.getByTestId(cancelAllOrdersBtn).should('be.visible'); diff --git a/apps/trading-e2e/src/integration/withdraw.cy.ts b/apps/trading-e2e/src/integration/withdraw.cy.ts index 225cadca1..ec14fd9a3 100644 --- a/apps/trading-e2e/src/integration/withdraw.cy.ts +++ b/apps/trading-e2e/src/integration/withdraw.cy.ts @@ -66,6 +66,9 @@ describe('withdraw form validation', { tags: '@smoke' }, () => { }); describe('withdraw actions', { tags: '@regression' }, () => { + // this is extremely ugly hack, but setting it properly in contract is too much effort for such simple validation + const withdrawalThreshold = + Cypress.env('VEGA_ENV') === 'CUSTOM' ? '0.00' : '100.00'; before(() => { cy.mockWeb3Provider(); cy.mockTradingPage(); @@ -96,7 +99,7 @@ describe('withdraw actions', { tags: '@regression' }, () => { ); cy.getByTestId('WITHDRAWAL_THRESHOLD_value').should( 'contain.text', - '100.00' + withdrawalThreshold ); cy.getByTestId('DELAY_TIME_label').should('contain.text', 'Delay time'); cy.getByTestId('DELAY_TIME_value').should('have.text', 'None'); diff --git a/apps/trading-e2e/src/support/create-order.ts b/apps/trading-e2e/src/support/create-order.ts new file mode 100644 index 000000000..80f1690ab --- /dev/null +++ b/apps/trading-e2e/src/support/create-order.ts @@ -0,0 +1,26 @@ +import type { OrderSubmission } from '@vegaprotocol/wallet'; + +const orderSizeField = 'order-size'; +const orderPriceField = 'order-price'; +const orderTIFDropDown = 'order-tif'; +const placeOrderBtn = 'place-order'; + +export const createOrder = (order: OrderSubmission): void => { + cy.log('Placing order', order); + const { type, side, size, price, timeInForce, expiresAt } = order; + + cy.getByTestId(`order-type-${type}`).click(); + cy.getByTestId(`order-side-${side}`).click(); + cy.getByTestId(orderSizeField).clear().type(size); + if (price) { + cy.getByTestId(orderPriceField).clear().type(price); + } + cy.getByTestId(orderTIFDropDown).select(timeInForce); + if (timeInForce === 'TIME_IN_FORCE_GTT') { + if (!expiresAt) { + throw new Error('Specify expiresAt if using GTT'); + } + cy.getByTestId('date-picker-field').type(expiresAt); + } + cy.getByTestId(placeOrderBtn).click(); +}; diff --git a/apps/trading-e2e/src/support/index.js b/apps/trading-e2e/src/support/index.js index d018c38cd..fff5a77ba 100644 --- a/apps/trading-e2e/src/support/index.js +++ b/apps/trading-e2e/src/support/index.js @@ -2,5 +2,6 @@ import '@vegaprotocol/cypress'; import 'cypress-real-events/support'; import registerCypressGrep from '@cypress/grep'; import { addMockTradingPage } from './trading'; + registerCypressGrep(); addMockTradingPage(); diff --git a/apps/trading-e2e/src/support/order-validation.ts b/apps/trading-e2e/src/support/order-validation.ts index 60ae94786..c270de7a3 100644 --- a/apps/trading-e2e/src/support/order-validation.ts +++ b/apps/trading-e2e/src/support/order-validation.ts @@ -61,7 +61,7 @@ const vegaWalletTransaction = (transaction: Transaction) => { .should('deep.equal', { token: JSON.parse(localStorage.getItem('vega_wallet_config') || '{}') ?.token, - publicKey: Cypress.env('VEGA_PUBLIC_KEY2'), + publicKey: Cypress.env('VEGA_PUBLIC_KEY'), sendingMode: 'TYPE_SYNC', transaction, }); diff --git a/apps/trading/.env.capsule b/apps/trading/.env.capsule new file mode 100644 index 000000000..b45714c8d --- /dev/null +++ b/apps/trading/.env.capsule @@ -0,0 +1,11 @@ +NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 +NX_ETHERSCAN_URL=https://sepolia.etherscan.io +NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz +NX_VEGA_CONFIG_URL='' +NX_VEGA_ENV=CUSTOM +NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz +NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"} +NX_VEGA_TOKEN_URL=https://token.fairground.wtf +NX_VEGA_URL=http://localhost:3028/query +NX_VEGA_WALLET_URL=http://localhost:1789 +NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet diff --git a/libs/cypress/src/index.ts b/libs/cypress/src/index.ts index f69f6a1b7..66587ab9a 100644 --- a/libs/cypress/src/index.ts +++ b/libs/cypress/src/index.ts @@ -15,6 +15,7 @@ import { addSetVegaWallet, } from './lib/commands/vega-wallet-connect'; import { addMockTransactionResponse } from './lib/commands/mock-transaction-response'; +import { addCreateMarket } from './lib/commands/create-market'; addGetTestIdcommand(); addSlackCommand(); @@ -31,11 +32,13 @@ addUpdateCapsuleMultiSig(); addVegaWalletConnect(); addSetVegaWallet(); addMockTransactionResponse(); +addCreateMarket(); export { mockConnectWallet } from './lib/commands/vega-wallet-connect'; export type { onMessage } from './lib/mock-ws'; export { aliasGQLQuery } from './lib/mock-gql'; export { aliasWalletQuery } from './lib/mock-rest'; +export * from './lib/utils'; Cypress.on( 'uncaught:exception', diff --git a/libs/cypress/src/lib/capsule/create-market.ts b/libs/cypress/src/lib/capsule/create-market.ts new file mode 100644 index 000000000..16a393b81 --- /dev/null +++ b/libs/cypress/src/lib/capsule/create-market.ts @@ -0,0 +1,137 @@ +import * as Schema from '@vegaprotocol/types'; +import { gql } from 'graphql-request'; +import { determineId } from '../utils'; +import { requestGQL, setEndpoints } from './request'; +import { vote } from './vote'; +import { setupEthereumAccount } from './ethereum-setup'; +import { faucetAsset } from './faucet-asset'; +import { + proposeMarket, + waitForEnactment, + waitForProposal, +} from './propose-market'; +import { createLog } from './logging'; + +const log = createLog('create-market'); + +export async function createMarket(cfg: { + vegaPubKey: string; + token: string; + ethWalletMnemonic: string; + ethereumProviderUrl: string; + vegaWalletUrl: string; + vegaUrl: string; + faucetUrl: string; +}) { + // set and store request endpoints + setEndpoints(cfg.vegaWalletUrl, cfg.vegaUrl); + + const markets = await getMarkets(); + + if (markets.length) { + log( + `${markets.length} market${ + markets.length > 1 ? 's' : '' + } found, skipping market creation` + ); + return markets; + } + + await setupEthereumAccount( + cfg.vegaPubKey, + cfg.ethWalletMnemonic, + cfg.ethereumProviderUrl + ); + + const result = await faucetAsset(cfg.faucetUrl, 'fUSDC', cfg.vegaPubKey); + if (!result.success) { + throw new Error('faucet failed'); + } + + // propose and vote on a market + const proposalTxResult = await proposeMarket(cfg.vegaPubKey, cfg.token); + const proposalId = determineId(proposalTxResult.transaction.signature.value); + log(`proposal created (id: ${proposalId})`); + const proposal = await waitForProposal(proposalId); + await vote( + proposal.id, + Schema.VoteValue.VALUE_YES, + cfg.vegaPubKey, + cfg.token + ); + await waitForEnactment(); + + // fetch and return created market + const newMarkets = await getMarkets(); + return newMarkets; +} + +async function getMarkets() { + const query = gql` + { + marketsConnection { + edges { + node { + id + decimalPlaces + positionDecimalPlaces + state + tradableInstrument { + instrument { + id + name + code + metadata { + tags + } + product { + ... on Future { + settlementAsset { + id + symbol + decimals + } + quoteName + } + } + } + } + } + } + } + } + `; + + const res = await requestGQL<{ + marketsConnection: { + edges: Array<{ + node: { + id: string; + decimalPlaces: number; + positionDecimalPlaces: number; + state: string; + tradableInstrument: { + instrument: { + id: string; + name: string; + code: string; + metadata: { + tags: string[]; + }; + product: { + settlementAssset: { + id: string; + symbol: string; + decimals: number; + }; + quoteName: string; + }; + }; + }; + }; + }>; + }; + }>(query); + + return res.marketsConnection.edges.map((e) => e.node); +} diff --git a/libs/cypress/src/lib/capsule/ethereum-setup.ts b/libs/cypress/src/lib/capsule/ethereum-setup.ts new file mode 100644 index 000000000..13268d41f --- /dev/null +++ b/libs/cypress/src/lib/capsule/ethereum-setup.ts @@ -0,0 +1,191 @@ +import { ethers, Wallet } from 'ethers'; +import { StakingBridge, Token } from '@vegaprotocol/smart-contracts'; +import { gql } from 'graphql-request'; +import { requestGQL } from './request'; +import { createLog } from './logging'; + +const log = createLog('ethereum-setup'); + +export async function setupEthereumAccount( + vegaPublicKey: string, + ethWalletMnemonic: string, + ethereumProviderUrl: string +) { + // create provider/wallet + const provider = new ethers.providers.JsonRpcProvider({ + url: ethereumProviderUrl, + }); + + const privateKey = Wallet.fromMnemonic( + ethWalletMnemonic, + getAccount() + ).privateKey; + + // this wallet (ozone access etc) is already set up with 6 million vega (eth) + const wallet = new Wallet(privateKey, provider); + + const vegaAsset = await getVegaAsset(); + if (!vegaAsset) { + throw new Error('could not fetch asset'); + } + + const ethereumConfig = await getEthereumConfig(); + if (!ethereumConfig) { + throw new Error('could not not fetch ethereum config'); + } + + const tokenContract = new Token(vegaAsset.source.contractAddress, wallet); + + log('sending approve tx'); + const approveTx = await promiseWithTimeout( + tokenContract.approve( + ethereumConfig.staking_bridge_contract.address, + '100000' + '0'.repeat(18) + ), + 1000, + 'tokenContract.approve' + ); + + await promiseWithTimeout( + approveTx.wait(1), + 10 * 60 * 1000, + 'approveTx.wait(1)' + ); + log('sending approve tx: success'); + + const stakingContract = new StakingBridge( + ethereumConfig.staking_bridge_contract.address, + wallet + ); + + const amount = '10000' + '0'.repeat(18); + log(`sending stake tx of ${amount} to ${vegaPublicKey}`); + const stakeTx = await promiseWithTimeout( + stakingContract.stake(amount, vegaPublicKey), + 14000, + 'stakingContract.stake(amount, vegaPublicKey)' + ); + await promiseWithTimeout(stakeTx.wait(3), 10 * 60 * 1000, 'stakeTx.wait(3)'); + await waitForStake(vegaPublicKey); + log(`sending stake tx: success`); +} + +function timeout(time = 0, id: string) { + return new Promise((resolve, reject) => { + setTimeout(() => reject(new Error(`${id}: timeout triggered`)), time); + }); +} + +async function promiseWithTimeout( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + promise: Promise, + time: number, + id: string +) { + return await Promise.race([promise, timeout(time, id)]); +} + +async function getVegaAsset() { + const query = gql` + { + assetsConnection { + edges { + node { + id + symbol + source { + ... on ERC20 { + contractAddress + } + } + } + } + } + } + `; + + const res = await requestGQL<{ + assetsConnection: { + edges: Array<{ + node: { + id: string; + symbol: string; + source: { + contractAddress: string; + }; + }; + }>; + }; + }>(query); + return res.assetsConnection.edges + .map((e) => e.node) + .find((a) => a.symbol === 'VEGA'); +} + +async function getEthereumConfig() { + const query = gql` + { + networkParameter(key: "blockchains.ethereumConfig") { + value + } + } + `; + + const res = await requestGQL<{ + networkParameter: { + key: string; + value: string; + }; + }>(query); + return JSON.parse(res.networkParameter.value); +} + +function waitForStake(vegaPublicKey: string) { + const query = gql` + { + party(id:"${vegaPublicKey}") { + stakingSummary { + currentStakeAvailable + } + } + } + `; + return new Promise((resolve, reject) => { + let tick = 1; + const interval = setInterval(async () => { + log(`confirming stake (attempt: ${tick})`); + if (tick >= 10) { + clearInterval(interval); + reject(new Error('stake link never seen')); + } + + try { + const res = await requestGQL<{ + party: { + stakingSummary: { + currentStakeAvailable: string; + }; + }; + }>(query); + + if ( + res.party?.stakingSummary?.currentStakeAvailable !== null && + parseInt(res.party.stakingSummary.currentStakeAvailable) > 0 + ) { + log( + `stake confirmed (amount: ${res.party.stakingSummary.currentStakeAvailable})` + ); + clearInterval(interval); + resolve(res.party.stakingSummary.currentStakeAvailable); + } + } catch (err) { + // no op, query will error until party is created + } + + tick++; + }, 1000); + }); +} + +// derivation path +const getAccount = (number = 0) => `m/44'/60'/0'/0/${number}`; diff --git a/libs/cypress/src/lib/capsule/faucet-asset.ts b/libs/cypress/src/lib/capsule/faucet-asset.ts new file mode 100644 index 000000000..60018bf94 --- /dev/null +++ b/libs/cypress/src/lib/capsule/faucet-asset.ts @@ -0,0 +1,23 @@ +import { createLog } from './logging'; + +const log = createLog('faucet-asset'); + +export async function faucetAsset( + url: string, + asset: string, + party: string, + amount = '10000' +) { + log(`sending ${amount} ${asset} to ${party}`); + const res = await fetch(url, { + method: 'post', + body: JSON.stringify({ + amount, + asset, + party, + }), + }); + const json = await res.json(); + + return json; +} diff --git a/libs/cypress/src/lib/capsule/logging.ts b/libs/cypress/src/lib/capsule/logging.ts new file mode 100644 index 000000000..67ebe79a4 --- /dev/null +++ b/libs/cypress/src/lib/capsule/logging.ts @@ -0,0 +1,5 @@ +export function createLog(name: string) { + return (message: string) => { + console.log(`[${name}]: ${message}`); + }; +} diff --git a/libs/cypress/src/lib/capsule/propose-market.ts b/libs/cypress/src/lib/capsule/propose-market.ts new file mode 100644 index 000000000..dae5e1bf3 --- /dev/null +++ b/libs/cypress/src/lib/capsule/propose-market.ts @@ -0,0 +1,192 @@ +import * as Schema from '@vegaprotocol/types'; +import { addSeconds, millisecondsToSeconds } from 'date-fns'; +import { gql } from 'graphql-request'; +import { request, requestGQL } from './request'; +import { createLog } from './logging'; +import type { ProposalSubmissionBody } from '@vegaprotocol/wallet'; + +const log = createLog('propose-market'); + +const MIN_CLOSE_SEC = 5; +const MIN_ENACT_SEC = 3; + +export async function proposeMarket(publicKey: string, token: string) { + log('sending proposal tx'); + const proposalTx = createNewMarketProposal(); + const result = await request('client.send_transaction', { + token, + publicKey, + sendingMode: 'TYPE_SYNC', + transaction: proposalTx, + }); + + return result.result; +} + +function createNewMarketProposal(): ProposalSubmissionBody { + 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: 'Add Lorem Ipsum market', + description: 'An example proposal to add Lorem Ipsum market', + }, + terms: { + newMarket: { + changes: { + decimalPlaces: '5', + positionDecimalPlaces: '5', + lpPriceRange: '10', + instrument: { + name: 'Test market 1', + code: 'TEST.24h', + future: { + settlementAsset: 'fUSDC', + quoteName: 'fUSDC', + dataSourceSpecForSettlementData: { + external: { + oracle: { + signers: [ + { + pubKey: { + key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC', + }, + }, + ], + 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 waitForProposal(id: string): Promise<{ id: string }> { + const query = gql` + { + proposal(id: "${id}") { + id + state + } + } + `; + return new Promise((resolve, reject) => { + let tick = 0; + const interval = setInterval(async () => { + if (tick >= 60) { + clearInterval(interval); + reject(new Error('proposal never seen')); + } + + try { + const res = await requestGQL<{ + proposal: { + id: string; + state: string; + }; + }>(query); + + if ( + res.proposal !== null && + res.proposal.state === Schema.ProposalState.STATE_OPEN + ) { + clearInterval(interval); + resolve(res.proposal); + } + } catch (err) { + console.log(err); + } + + tick++; + }, 1000); + }); +} + +export function waitForEnactment() { + const timeout = MIN_CLOSE_SEC * 1000 + MIN_ENACT_SEC * 1000; + return new Promise((resolve) => { + setTimeout(resolve, timeout + 2000); + }); +} diff --git a/libs/cypress/src/lib/capsule/request.ts b/libs/cypress/src/lib/capsule/request.ts new file mode 100644 index 000000000..09814de47 --- /dev/null +++ b/libs/cypress/src/lib/capsule/request.ts @@ -0,0 +1,39 @@ +import { request as gqlRequest } from 'graphql-request'; + +let walletEndpoint = ''; +let gqlEndpoint = ''; + +export function setEndpoints(walletUrl: string, gqlUrl: string) { + walletEndpoint = walletUrl + '/api/v2/requests'; + gqlEndpoint = gqlUrl; +} + +export function request(method: string, params: object) { + if (!walletEndpoint) { + throw new Error('gqlEndpoint not set'); + } + const body = { + jsonrpc: '2.0', + method, + params, + id: Math.random().toString(), + }; + return fetch(walletEndpoint, { + method: 'post', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + Origin: 'market-setup', + Referer: 'market-setup', + }, + }).then((res) => { + return res.json(); + }); +} + +export function requestGQL(query: string): Promise { + if (!gqlEndpoint) { + throw new Error('gqlEndpoint not set'); + } + return gqlRequest(gqlEndpoint, query); +} diff --git a/libs/cypress/src/lib/capsule/vote.ts b/libs/cypress/src/lib/capsule/vote.ts new file mode 100644 index 000000000..a987ce0a0 --- /dev/null +++ b/libs/cypress/src/lib/capsule/vote.ts @@ -0,0 +1,33 @@ +import type * as Schema from '@vegaprotocol/types'; +import { request } from './request'; +import { createLog } from './logging'; + +const log = createLog('vote'); + +export async function vote( + proposalId: string, + voteValue: Schema.VoteValue, + publicKey: string, + token: string +) { + log(`voting ${voteValue} on ${proposalId}`); + + const voteTx = createVote(proposalId, voteValue); + const voteResult = await request('client.send_transaction', { + token, + publicKey, + sendingMode: 'TYPE_SYNC', + transaction: voteTx, + }); + + return voteResult.result; +} + +function createVote(proposalId: string, value: Schema.VoteValue) { + return { + voteSubmission: { + value, + proposalId, + }, + }; +} diff --git a/libs/cypress/src/lib/commands/create-market.ts b/libs/cypress/src/lib/commands/create-market.ts new file mode 100644 index 000000000..6ee165c52 --- /dev/null +++ b/libs/cypress/src/lib/commands/create-market.ts @@ -0,0 +1,50 @@ +import { createMarket } from '../capsule/create-market'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + createMarket(): void; + } + } +} + +// suppress fetch and xhr logs +const originalLog = Cypress.log; +// @ts-ignore fuck with log to help with debug +Cypress.log = function (options, ...rest) { + // @ts-ignore fuck with log to help with debug + const isRequest = ['fetch', 'xhr'].includes(options.displayName); + if (isRequest) { + return; + } + + return originalLog(options, ...rest); +}; + +export const addCreateMarket = () => { + Cypress.Commands.add('createMarket', () => { + const config = { + vegaPubKey: Cypress.env('VEGA_PUBLIC_KEY'), + token: Cypress.env('VEGA_WALLET_API_TOKEN'), + ethWalletMnemonic: Cypress.env('ETH_WALLET_MNEMONIC'), + ethereumProviderUrl: Cypress.env('ETHEREUM_PROVIDER_URL'), + vegaWalletUrl: Cypress.env('VEGA_WALLET_URL'), + vegaUrl: Cypress.env('VEGA_URL'), + faucetUrl: Cypress.env('FAUCET_URL'), + }; + + cy.highlight('creating market on capsule environment'); + + cy.wrap(createMarket(config), { + timeout: 5 * 60 * 1000, + }) + // register market list result so it can be retrieved in tests later + .as('markets'); + + // make sure we have a market to test against, createMarket will + // return an array of markets or false if setup failed + cy.get('@markets').should('not.equal', false); + }); +}; diff --git a/libs/cypress/src/lib/mock-gql.ts b/libs/cypress/src/lib/mock-gql.ts index 9aa3dbfa5..90e2b80a0 100644 --- a/libs/cypress/src/lib/mock-gql.ts +++ b/libs/cypress/src/lib/mock-gql.ts @@ -21,7 +21,7 @@ const hasOperationName = ( export function addMockGQLCommand() { Cypress.Commands.add('mockGQL', (handler: RouteHandler) => { - cy.intercept('POST', '**/graphql', handler).as('GQL'); + cy.intercept('POST', Cypress.env('VEGA_URL'), handler).as('GQL'); }); } diff --git a/libs/cypress/src/lib/utils.ts b/libs/cypress/src/lib/utils.ts new file mode 100644 index 000000000..1a63339fc --- /dev/null +++ b/libs/cypress/src/lib/utils.ts @@ -0,0 +1,20 @@ +import { ethers } from 'ethers'; +import sha3 from 'js-sha3'; +import BigNumber from 'bignumber.js'; + +/** + * copy of determineId in libs/wallet/src/utils.ts + * to avoid pulling in any jsx files which will cypress is not set up to compile + */ +export function determineId(sig: string) { + return sha3.sha3_256(ethers.utils.arrayify('0x' + sig)); +} + +/** + * copy of removeDecimal from libs/react-helpers/src/lib/format/number.tsx + * to avoid pulling in any jsx files which will cypress is not set up to compile + */ +export function removeDecimal(value: string, decimals: number): string { + if (!decimals) return value; + return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0); +} diff --git a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx index f19d7f44e..78c5ce727 100644 --- a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx +++ b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx @@ -47,6 +47,16 @@ export const MarketListTable = forwardRef< ) => { + if (!data) return null; + return {value}; + }} /> ( /> ) => { - return value ? getDateTimeFormat().format(new Date(value)) : value; + }: VegaICellRendererParams) => { + return ( + + {value ? getDateTimeFormat().format(new Date(value)) : value} + + ); }} /> ) => { - return value ? getDateTimeFormat().format(new Date(value)) : '-'; + }: VegaICellRendererParams) => { + return ( + + {value ? getDateTimeFormat().format(new Date(value)) : '-'} + + ); }} />