From 3833bed38fb6926b3edd20a32872798214671ab2 Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 3 Feb 2022 15:23:52 +0100 Subject: [PATCH 01/15] test ci --- .github/workflows/deploy-stage.yml | 39 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index d3426bffa2..83393eb6f9 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -5,6 +5,7 @@ on: - dev - breaking - hotfix/* + - build-runner-gcp tags: - hotfix-* @@ -24,7 +25,7 @@ jobs: build-images: name: Build and Push Docker Images - runs-on: [self-hosted, MAIN] + runs-on: [self-hosted, ci-runner] steps: - uses: actions/checkout@v2 @@ -55,21 +56,21 @@ jobs: docker-compose down # Reminder: when disabling the deploy stage - comment the whole job out! - deploy: - name: Deploy Stage environment - runs-on: [k8s, deployer, stage] - needs: [setup, build-images] - steps: - - name: Deploy - uses: aurelien-baudet/workflow-dispatch@v2 - with: - workflow: Deploy - token: ${{ secrets.GH_TOKEN }} - wait-for-completion-timeout: 10m - wait-for-completion-interval: 1m - inputs: | - { - "environment": "stage", - "image_tag": "${{ needs.setup.outputs.image_tag }}", - "config_ref": "master" - } + # deploy: + # name: Deploy Stage environment + # runs-on: [k8s, deployer, stage] + # needs: [setup, build-images] + # steps: + # - name: Deploy + # uses: aurelien-baudet/workflow-dispatch@v2 + # with: + # workflow: Deploy + # token: ${{ secrets.GH_TOKEN }} + # wait-for-completion-timeout: 10m + # wait-for-completion-interval: 1m + # inputs: | + # { + # "environment": "stage", + # "image_tag": "${{ needs.setup.outputs.image_tag }}", + # "config_ref": "master" + # } From c61efccce397a80e3b585c19cbed9a74e4a943bd Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 3 Feb 2022 15:39:45 +0100 Subject: [PATCH 02/15] install rust and cargo --- .github/workflows/deploy-stage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 83393eb6f9..034a0aaf0c 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -37,6 +37,7 @@ jobs: - name: init run: | + curl https://sh.rustup.rs -sSf | sh cargo sqlx --version || cargo install sqlx-cli zk zk run yarn From 705f5eca23af28bdafd908e5d4e5da06dd5a60bf Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 3 Feb 2022 15:41:11 +0100 Subject: [PATCH 03/15] test --- .github/workflows/deploy-stage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 034a0aaf0c..260ad3390c 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -37,7 +37,7 @@ jobs: - name: init run: | - curl https://sh.rustup.rs -sSf | sh + curl https://sh.rustup.rs -sSf | sh -y cargo sqlx --version || cargo install sqlx-cli zk zk run yarn From e268eab562c292ca61adfda2af6b175dadde4a46 Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 3 Feb 2022 15:46:14 +0100 Subject: [PATCH 04/15] fix --- .github/workflows/deploy-stage.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 260ad3390c..1268b737dd 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -37,7 +37,9 @@ jobs: - name: init run: | - curl https://sh.rustup.rs -sSf | sh -y + curl https://sh.rustup.rs -sSf -o install.sh + chmod +x install.sh + ./install.sh -y cargo sqlx --version || cargo install sqlx-cli zk zk run yarn From 240c19f92613dcfc2e8dba6761e2d19572f4c41f Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 3 Feb 2022 15:47:47 +0100 Subject: [PATCH 05/15] source $HOME/.cargo/env --- .github/workflows/deploy-stage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 1268b737dd..372f2504f3 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -40,6 +40,7 @@ jobs: curl https://sh.rustup.rs -sSf -o install.sh chmod +x install.sh ./install.sh -y + source $HOME/.cargo/env cargo sqlx --version || cargo install sqlx-cli zk zk run yarn From 83bf58d59d1992cbda961261784fb8e2a88db4b4 Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 3 Feb 2022 15:52:25 +0100 Subject: [PATCH 06/15] pkg-config --- .github/workflows/deploy-stage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 372f2504f3..5d1e8293cb 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -41,6 +41,7 @@ jobs: chmod +x install.sh ./install.sh -y source $HOME/.cargo/env + apt-get install -y pkg-config cargo sqlx --version || cargo install sqlx-cli zk zk run yarn From 7ee28c5c5421050b7db095d7aa50fa43706faf35 Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Thu, 17 Feb 2022 18:19:17 +0100 Subject: [PATCH 07/15] test docker build --- .github/workflows/deploy-stage.yml | 27 +++++++++++++-------------- docker-compose-runner.yml | 1 + 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 5d1e8293cb..323dbbf890 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -35,30 +35,29 @@ jobs: echo CI=1 >> $GITHUB_ENV echo $(pwd)/bin >> $GITHUB_PATH + - name: start-services + run: | + docker-compose -f docker-compose-runner.yml down + docker-compose -f docker-compose-runner.yml up --build -d zk + - name: init run: | - curl https://sh.rustup.rs -sSf -o install.sh - chmod +x install.sh - ./install.sh -y - source $HOME/.cargo/env - apt-get install -y pkg-config - cargo sqlx --version || cargo install sqlx-cli - zk - zk run yarn + ci_run zk + ci_run zk run yarn cp etc/tokens/{test,localhost}.json - zk run verify-keys unpack - zk up - zk db basic-setup + ci_run zk run verify-keys unpack + ci_run zk up + ci_run zk db basic-setup - name: update-images run: | - docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }} - zk docker push rust + ci_run docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }} + ci_run zk docker push rust - name: docker-down if: always() run: | - docker-compose down + docker-compose -f docker-compose-runner.yml down # Reminder: when disabling the deploy stage - comment the whole job out! # deploy: diff --git a/docker-compose-runner.yml b/docker-compose-runner.yml index 851fbaca6c..60a68c664b 100644 --- a/docker-compose-runner.yml +++ b/docker-compose-runner.yml @@ -35,6 +35,7 @@ services: - /usr/src/cache:/usr/src/cache - /usr/src/keys:/usr/src/keys - /etc/sa_secret:/etc/sa_secret + - /var/run/docker.sock:/var/run/docker.sock environment: - IN_DOCKER=true - CACHE_DIR=/usr/src/cache From bbb5676e5bd6bd49de25feb225247ee211dffadd Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Mon, 21 Feb 2022 10:42:00 +0100 Subject: [PATCH 08/15] moved pg up to docker compose --- .github/workflows/deploy-stage.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 323dbbf890..3cb5413886 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -38,15 +38,15 @@ jobs: - name: start-services run: | docker-compose -f docker-compose-runner.yml down - docker-compose -f docker-compose-runner.yml up --build -d zk + docker-compose -f docker-compose-runner.yml up -d zk postgres - name: init run: | ci_run zk ci_run zk run yarn - cp etc/tokens/{test,localhost}.json + ci_run cp etc/tokens/{test,localhost}.json ci_run zk run verify-keys unpack - ci_run zk up + # ci_run zk up ci_run zk db basic-setup - name: update-images From bfa250db3849f36c10c8a32bca95cb7aee00b1c8 Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Mon, 21 Feb 2022 20:20:25 +0100 Subject: [PATCH 09/15] moved build to template --- .github/workflows/deploy-stage.yml | 77 +++++++-------------- .github/workflows/template-docker-build.yml | 51 ++++++++++++++ 2 files changed, 76 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/template-docker-build.yml diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 3cb5413886..03985527b3 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -5,7 +5,6 @@ on: - dev - breaking - hotfix/* - - build-runner-gcp tags: - hotfix-* @@ -25,56 +24,30 @@ jobs: build-images: name: Build and Push Docker Images - runs-on: [self-hosted, ci-runner] - - steps: - - uses: actions/checkout@v2 - - name: setup-env - run: | - echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV - echo CI=1 >> $GITHUB_ENV - echo $(pwd)/bin >> $GITHUB_PATH - - - name: start-services - run: | - docker-compose -f docker-compose-runner.yml down - docker-compose -f docker-compose-runner.yml up -d zk postgres - - - name: init - run: | - ci_run zk - ci_run zk run yarn - ci_run cp etc/tokens/{test,localhost}.json - ci_run zk run verify-keys unpack - # ci_run zk up - ci_run zk db basic-setup - - - name: update-images - run: | - ci_run docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }} - ci_run zk docker push rust - - - name: docker-down - if: always() - run: | - docker-compose -f docker-compose-runner.yml down + needs: [setup] + uses: ./.github/workflows/template-docker-build.yml + secrets: + DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + with: + image_tag: ${{ needs.setup.outputs.image_tag }} # Reminder: when disabling the deploy stage - comment the whole job out! - # deploy: - # name: Deploy Stage environment - # runs-on: [k8s, deployer, stage] - # needs: [setup, build-images] - # steps: - # - name: Deploy - # uses: aurelien-baudet/workflow-dispatch@v2 - # with: - # workflow: Deploy - # token: ${{ secrets.GH_TOKEN }} - # wait-for-completion-timeout: 10m - # wait-for-completion-interval: 1m - # inputs: | - # { - # "environment": "stage", - # "image_tag": "${{ needs.setup.outputs.image_tag }}", - # "config_ref": "master" - # } + deploy: + name: Deploy Stage environment + runs-on: [k8s, deployer, stage] + needs: [setup, build-images] + steps: + - name: Deploy + uses: aurelien-baudet/workflow-dispatch@v2 + with: + workflow: Deploy + token: ${{ secrets.GH_TOKEN }} + wait-for-completion-timeout: 10m + wait-for-completion-interval: 1m + inputs: | + { + "environment": "stage", + "image_tag": "${{ needs.setup.outputs.image_tag }}", + "config_ref": "master" + } diff --git a/.github/workflows/template-docker-build.yml b/.github/workflows/template-docker-build.yml new file mode 100644 index 0000000000..d07b44ec9c --- /dev/null +++ b/.github/workflows/template-docker-build.yml @@ -0,0 +1,51 @@ +name: Build docker image +on: + workflow_call: + secrets: + DOCKERHUB_USER: + description: 'DOCKERHUB_USER' + required: true + DOCKERHUB_TOKEN: + description: 'DOCKERHUB_TOKEN' + required: true + inputs: + image_tag: + description: 'Tag of a built image to deploy' + type: string + required: true + +jobs: + build-images: + name: Build and Push Docker Images + env: + image_tag: ${{ inputs.image_tag }} + runs-on: [self-hosted, ci-runner] + steps: + - uses: actions/checkout@v2 + - name: setup-env + run: | + echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV + echo CI=1 >> $GITHUB_ENV + echo $(pwd)/bin >> $GITHUB_PATH + + - name: start-services + run: | + docker-compose -f docker-compose-runner.yml up -d zk postgres + + - name: init + run: | + ci_run zk + ci_run zk run yarn + ci_run cp etc/tokens/{test,localhost}.json + ci_run zk run verify-keys unpack + ci_run zk db basic-setup + + - name: update-images + run: | + ci_run docker login -u ${{ secrets.DOCKERHUB_USER }} -p ${{ secrets.DOCKERHUB_TOKEN }} + ci_run zk docker push server + + - name: docker-down + if: always() + run: | + docker-compose -f docker-compose-runner.yml down From 979ccc8ad2e196a0f0eafc8bec1455b3e557ee49 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 22 Feb 2022 17:09:31 +0300 Subject: [PATCH 10/15] Remove zcli tool --- .github/workflows/ci.yml | 18 +- .gitignore | 2 - bin/zcli | 9 - docs/architecture.md | 1 - infrastructure/zcli/.gitignore | 3 - infrastructure/zcli/README.md | 135 -------- infrastructure/zcli/package.json | 33 -- infrastructure/zcli/src/commands.ts | 229 ------------- infrastructure/zcli/src/config.ts | 47 --- infrastructure/zcli/src/index.ts | 164 ---------- infrastructure/zcli/src/types.ts | 45 --- infrastructure/zcli/test/commands.test.ts | 377 ---------------------- infrastructure/zcli/tsconfig.json | 15 - infrastructure/zk/src/test/integration.ts | 13 - package.json | 11 - yarn.lock | 36 +-- 16 files changed, 8 insertions(+), 1130 deletions(-) delete mode 100755 bin/zcli delete mode 100644 infrastructure/zcli/.gitignore delete mode 100644 infrastructure/zcli/README.md delete mode 100644 infrastructure/zcli/package.json delete mode 100644 infrastructure/zcli/src/commands.ts delete mode 100644 infrastructure/zcli/src/config.ts delete mode 100644 infrastructure/zcli/src/index.ts delete mode 100644 infrastructure/zcli/src/types.ts delete mode 100644 infrastructure/zcli/test/commands.test.ts delete mode 100644 infrastructure/zcli/tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a2dbdc07f..7965b516f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,8 @@ on: pull_request: push: branches: - - staging - - trying + - staging + - trying jobs: lint: @@ -121,9 +121,6 @@ jobs: ci_run zk test i api ci_run zk test i api-docs - - name: integration-zcli - run: ci_run zk test i zcli - - name: integration-rust-sdk run: ci_run zk test i rust-sdk @@ -147,7 +144,7 @@ jobs: run: | echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV echo $(pwd)/bin >> $GITHUB_PATH - + - name: start-services run: | docker-compose -f docker-compose-runner.yml down @@ -234,7 +231,6 @@ jobs: docker-compose -f docker-compose-runner.yml exec -T -e ALLOWED_PERCENT=20 -e RUST_LOG=loadnext=debug -e ZKSYNC_RPC_ADDR=http://127.0.0.2:3030 -e WEB3_URL=http://geth:8545 -e ETH_NETWORK=localhost -e MASTER_WALLET_PK=74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998 -e ACCOUNTS_AMOUNT=5 -e OPERATIONS_PER_ACCOUNT=5 -e MAIN_TOKEN=DAI zk ./target/release/loadnext docker-compose -f docker-compose-runner.yml exec -T -e ZKSYNC_REST_ADDR=http://127.0.0.2:3001 zk ts-node core/tests/check-block-root-hahes.ts - - name: stop-server run: | ci_run killall zksync_server @@ -249,22 +245,20 @@ jobs: ci_run sleep 30 docker-compose -f docker-compose-runner.yml exec -T -e ZKSYNC_REST_ADDR=http://127.0.0.2:3001 zk ts-node core/tests/check-block-root-hahes.ts - - name: Show logs if: always() run: | ci_run cat server.log ci_run cat api.log - notify: if: always() name: Notify on failures runs-on: ubuntu-latest - needs: [lint, unit-tests, integration, circuit-tests, testkit, revert-blocks] + needs: + [lint, unit-tests, integration, circuit-tests, testkit, revert-blocks] steps: - - - if: failure() + - if: failure() name: Notify to Mattermost (on incidents) uses: tferreira/matterfy@releases/v1 with: diff --git a/.gitignore b/.gitignore index de191face4..a83a113786 100644 --- a/.gitignore +++ b/.gitignore @@ -46,8 +46,6 @@ go_to_env.sh core/lib/storage/.env -.zcli-config.json - # Perf/flamegraph files perf.data* flamegraph.svg diff --git a/bin/zcli b/bin/zcli deleted file mode 100755 index c793ad29e6..0000000000 --- a/bin/zcli +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -if [ -z "$1" ]; then - cd $ZKSYNC_HOME - yarn && yarn zcli build -else - # can't start this with yarn since it has quirks with `--` as an argument - node -- $ZKSYNC_HOME/infrastructure/zcli/build/index.js "$@" -fi diff --git a/docs/architecture.md b/docs/architecture.md index d71108da3b..35158767c8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -90,7 +90,6 @@ This section provides an overview on folders / sub-projects that exist in this r - `/tokens`: Configuration of supported Ethereum ERC-20 tokens. - `/infrastructure`: Application that aren't naturally a part of zkSync core, but are related to it. - `/explorer`: A blockchain explorer for zkSync network. - - `/zcli`: A command-line interface and development wallet for zkSync network. - `/keys`: Verification keys for `circuit` module. - `/sdk`: Implementation of client libraries for zkSync network in different programming languages. - `/zksync-crypto`: zkSync network cryptographic primitives, which can be compiled to WASM. diff --git a/infrastructure/zcli/.gitignore b/infrastructure/zcli/.gitignore deleted file mode 100644 index 61ac191923..0000000000 --- a/infrastructure/zcli/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build -/node_modules -/.zcli-config.json diff --git a/infrastructure/zcli/README.md b/infrastructure/zcli/README.md deleted file mode 100644 index 19ab025284..0000000000 --- a/infrastructure/zcli/README.md +++ /dev/null @@ -1,135 +0,0 @@ -# `zcli` - command line interface to zkSync - -## Configuration - -Config file `.zcli-config.json` is auto-generated and is not to be edited. - -Config management can be done through the CLI. - -By default, `zcli` tries to open `./.zcli-config.json`. If not present, `$ZCLI_HOME/.zcli-config.json`. - -## Usage - -All output printed to `stdout` is strict `JSON` and parseable by `jq`. - -`--help` and `--version` options are available (not `JSON`). - -If any error occures, process exits with error code 1. - -### Wallet management - -All wallets are stored as unencrypted private keys in `.zcli-config.json`. One of them may be set as default wallet. - -```bash -# lists all wallets' addresses -zcli wallets - -# prints address of the default wallet (or null) -zcli wallets default - -# sets ADDRESS as a default wallet -zcli wallets default ADDRESS - -# adds a wallet to config -# if key is not provided, creates a random wallet -# if default wallet was not set - sets it as default -zcli wallets add [PRIVATE_KEY] - -# removes ADDRESS from wallets -zcli wallets delete ADDRESS -``` - -### Network management - -In every command, default network may be overriden by `-n NETWORK` flag. `NETWORK` can be either `localhost`, `rinkeby`, -`ropsten` or `mainnet`. - -```bash -# list available networks -zcli networks - -# print default network -zcli networks default - -# set default network to NETWORK -zcli networks default NETWORK -``` - -### Fetching information - -```bash -# prints info about account - nonce, id, balances etc. -# by default ADDRESS is set to default wallet -zcli account [ADDRESS] - -# prints info about transaction - from, to, amount, token, fee etc. -zcli transaction TX_HASH - -# same as transaction, but first waits until it's commited/verified -# -t flag supplies timeout, after which the command returns null (default: 60) -zcli await commit [-t SECONDS] TX_HASH -zcli await verify [-t SECONDS] TX_HASH -``` - -### Creating transactions - -```bash -# makes a deposit from default wallet to ADDRESS -# by default ADDRESS is set to default wallet -zcli deposit [--fast] AMOUNT TOKEN [ADDRESS] - -# makes a deposit from wallet with PRIVATE_KEY to ADDRESS -zcli deposit [--fast] --json '{ amount: AMOUNT, token: TOKEN, from: PRIVATE_KEY, to: ADDRESS }' - -# makes a transfer from default wallet to ADDRESS -zcli transfer [--fast] AMOUNT TOKEN ADDRESS - -# makes a deposit from wallet with PRIVATE_KEY to ADDRESS -zcli transfer [--fast] --json '{ amount: AMOUNT, token: TOKEN, from: PRIVATE_KEY, to: ADDRESS }' -``` - -If `--fast` is set, the command will not wait for transaction commitment and only print the transaction hash. Otherwise, -full information about transaction is printed. - -## Installation - -After (if) this package is published to `npm`, installation is as easy as - -```bash -yarn global add zcli -``` - -## Example usage - -``` -$ zcli netwokrks default ropsten -"ropsten" -$ zcli wallets add 0x8888888888888888888888888888888888888888888888888888888888888888 -"0x62f94E9AC9349BCCC61Bfe66ddAdE6292702EcB6" -$ zcli deposit 3.14 ETH -{ - "network": "ropsten", - "transaction": { - "status": "success", - "from": "0x62f94E9AC9349BCCC61Bfe66ddAdE6292702EcB6", - "to": "0x62f94E9AC9349BCCC61Bfe66ddAdE6292702EcB6", - "hash": "0x602de5abbdbb9ab1861cf04c8580e8b0d6bee9f16d6dfbf2c08d8aa624241115", - "operation": "Deposit", - "nonce": -1, - "amount": "3.14", - "token": "ETH" - } -} -$ zcli transfer --fast 3.0 ETH 0x36615cf349d7f6344891b1e7ca7c72883f5dc049 -"sync-tx:f945ace556a6576e05c38a0fcca29f40674ea9a14d49c099b51a12737d9dac7b" -$ zcli account 0x36615cf349d7f6344891b1e7ca7c72883f5dc049 -{ - "network": "ropsten", - "address": "0x36615cf349d7f6344891b1e7ca7c72883f5dc049", - "account_id": 5, - "nonce": 2, - "balances": { - "ETH": "3.0" - } -} -``` diff --git a/infrastructure/zcli/package.json b/infrastructure/zcli/package.json deleted file mode 100644 index c6d9ecfb5c..0000000000 --- a/infrastructure/zcli/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "zcli", - "version": "0.1.0", - "main": "build/index.js", - "license": "MIT", - "bin": "build/index.js", - "scripts": { - "build": "tsc", - "watch": "tsc --watch", - "start": "node build/index.js", - "test": "zk f mocha --no-timeout -r ts-node/register test/*.test.ts" - }, - "dependencies": { - "commander": "^6.2.0", - "ethers": "^5.4.4", - "isomorphic-fetch": "^3.0.0", - "zksync": "link:../../sdk/zksync.js" - }, - "devDependencies": { - "@types/chai": "^4.2.14", - "@types/chai-as-promised": "^7.1.3", - "@types/isomorphic-fetch": "^0.0.35", - "@types/mocha": "^8.0.3", - "@types/mock-fs": "^4.13.0", - "@types/node": "^14.14.5", - "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", - "mocha": "^8.2.0", - "mock-fs": "^4.13.0", - "ts-node": "^9.0.0", - "typescript": "^4.0.5" - } -} diff --git a/infrastructure/zcli/src/commands.ts b/infrastructure/zcli/src/commands.ts deleted file mode 100644 index 47c69e24d7..0000000000 --- a/infrastructure/zcli/src/commands.ts +++ /dev/null @@ -1,229 +0,0 @@ -import 'isomorphic-fetch'; -import * as zksync from 'zksync'; -import * as ethers from 'ethers'; -import { saveConfig } from './config'; -import { ALL_NETWORKS, Network, Config, AccountInfo, TxInfo, TxDetails } from './types'; - -export function apiServer(network: Network) { - const servers = { - localhost: 'http://localhost:3001', - ropsten: 'https://ropsten-api.zksync.io', - rinkeby: 'https://rinkeby-api.zksync.io', - mainnet: 'https://api.zksync.io' - }; - return `${servers[network]}/api/v0.1`; -} - -export async function accountInfo(address: string, network: Network = 'localhost'): Promise { - const provider = await zksync.getDefaultProvider(network, 'HTTP'); - const state = await provider.getState(address); - let balances: { [token: string]: string } = {}; - for (const token in state.committed.balances) { - balances[token] = provider.tokenSet.formatToken(token, state.committed.balances[token]); - } - await provider.disconnect(); - return { - address, - network, - account_id: state.id, - nonce: state.committed.nonce, - balances - }; -} - -export async function txInfo( - tx_hash: string, - network: Network = 'localhost', - wait: '' | 'COMMIT' | 'VERIFY' = '' -): Promise { - const provider = await zksync.getDefaultProvider(network, 'HTTP'); - if (wait !== '') { - await provider.notifyTransaction(tx_hash, wait); - } - const api_url = `${apiServer(network)}/transactions_all/${tx_hash}`; - const response = await fetch(api_url); - const tx = await response.json(); - if (tx === null) { - await provider.disconnect(); - return { - network, - transaction: null - }; - } - let info: TxInfo = { - network, - transaction: { - status: tx.fail_reason ? 'error' : 'success', - from: tx.from, - to: tx.to, - hash: tx_hash, - operation: tx.tx_type, - nonce: tx.nonce - } - }; - if (tx.token === -1) { - await provider.disconnect(); - return info; - } - const tokens = await provider.getTokens(); - await provider.disconnect(); - const tokenInfo = Object.values(tokens).find((value) => value.id == tx.token); - if (tokenInfo) { - const token = tokenInfo.symbol; // @ts-ignore - info.transaction.amount = - tx.amount == 'unknown amount' ? null : provider.tokenSet.formatToken(token, tx.amount); - if (tx.fee) { - // @ts-ignore - info.transaction.fee = provider.tokenSet.formatToken(token, tx.fee); - } // @ts-ignore - info.transaction.token = token; - } else { - throw new Error('token not found'); - } - return info; -} - -export async function availableNetworks() { - let networks: Network[] = []; - for (const network of ALL_NETWORKS) { - try { - const provider = await zksync.getDefaultProvider(network, 'HTTP'); - await provider.disconnect(); - networks.push(network); - } catch (err) { - /* could not connect to provider */ - } - } - return networks; -} - -export function defaultNetwork(config: Config, network?: Network) { - if (network) { - if (ALL_NETWORKS.includes(network)) { - config.network = network; - saveConfig(config); - } else { - throw new Error('invalid network name'); - } - } - return config.network; -} - -export function addWallet(config: Config, privkey?: string) { - const wallet = privkey ? new ethers.Wallet(privkey) : ethers.Wallet.createRandom(); - const address = wallet.address.toLowerCase(); - config.wallets[address] = wallet.privateKey; - if (!config.defaultWallet) { - config.defaultWallet = address; - } - saveConfig(config); - return wallet.address; -} - -export function listWallets(config: Config) { - return Object.keys(config.wallets); -} - -export function removeWallet(config: Config, address: string) { - address = address.toLowerCase(); - delete config.wallets[address]; - if (config.defaultWallet === address) { - config.defaultWallet = null; - } - saveConfig(config); -} - -export function defaultWallet(config: Config, address?: string) { - if (address) { - address = address.toLowerCase(); - if (config.wallets.hasOwnProperty(address)) { - config.defaultWallet = address; - saveConfig(config); - } else { - throw new Error('address is not present'); - } - } - return config.defaultWallet; -} - -class TxSubmitter { - private constructor(private syncProvider: zksync.Provider, private syncWallet: zksync.Wallet) {} - - static async submit( - type: 'deposit' | 'transfer' | 'withdraw', - txDetails: TxDetails, - fast: boolean = false, - network: Network = 'localhost' - ) { - let ethProvider; - if (process.env.CI == '1') { - ethProvider = new ethers.providers.JsonRpcProvider('http://geth:8545'); - } else if (network == 'localhost') { - ethProvider = new ethers.providers.JsonRpcProvider(); - } else { - ethProvider = ethers.getDefaultProvider(network); - } - const syncProvider = await zksync.getDefaultProvider(network, 'HTTP'); - const ethWallet = new ethers.Wallet(txDetails.privkey).connect(ethProvider); - const syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider); - const submitter = new TxSubmitter(syncProvider, syncWallet); - const hash = await submitter[type](txDetails, fast); - await submitter.syncProvider.disconnect(); - return hash; - } - - private async transfer(txDetails: TxDetails, fast: boolean) { - const { to, token, amount } = txDetails; - if (!(await this.syncWallet.isSigningKeySet())) { - const changePubkey = await this.syncWallet.setSigningKey({ - feeToken: token, - ethAuthType: 'ECDSA' - }); - await changePubkey.awaitReceipt(); - } - const txHandle = await this.syncWallet.syncTransfer({ - to, - token, - amount: this.syncProvider.tokenSet.parseToken(token, amount) - }); - if (!fast) await txHandle.awaitReceipt(); - return txHandle.txHash; - } - - private async deposit(txDetails: TxDetails, fast: boolean) { - const { to: depositTo, token, amount } = txDetails; - const depositHandle = await this.syncWallet.depositToSyncFromEthereum({ - depositTo, - token, - amount: this.syncProvider.tokenSet.parseToken(token, amount), - approveDepositAmountForERC20: !zksync.utils.isTokenETH(token) - }); - if (!fast) await depositHandle.awaitReceipt(); - return depositHandle.ethTx.hash; - } - - private async withdraw(txDetails: TxDetails, fast: boolean) { - const { to: ethAddress, token, amount, fastProcessing } = txDetails; - if (!(await this.syncWallet.isSigningKeySet())) { - const changePubkey = await this.syncWallet.setSigningKey({ - feeToken: token, - ethAuthType: 'ECDSA' - }); - await changePubkey.awaitReceipt(); - } - const txHandle = await this.syncWallet.withdrawFromSyncToEthereum({ - ethAddress, - token, - amount: this.syncProvider.tokenSet.parseToken(token, amount), - fastProcessing - }); - if (!fast) await txHandle.awaitReceipt(); - return txHandle.txHash; - } -} - -export const submitTx = TxSubmitter.submit; -export const deposit = async (details: TxDetails, fast: boolean = false, network: Network = 'localhost') => - await submitTx('deposit', details, fast, network); -export const transfer = async (details: TxDetails, fast: boolean = false, network: Network = 'localhost') => - await submitTx('transfer', details, fast, network); diff --git a/infrastructure/zcli/src/config.ts b/infrastructure/zcli/src/config.ts deleted file mode 100644 index cdd2c3b81e..0000000000 --- a/infrastructure/zcli/src/config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import fs from 'fs'; -import { Config, ALL_NETWORKS } from './types'; -import assert from 'assert'; - -const CONFIG_FILE = '.zcli-config.json'; -export const DEFAULT_CONFIG: Config = { - network: 'localhost', - defaultWallet: null, - wallets: {} -}; - -export function configLocation() { - const in_pwd = './' + CONFIG_FILE; - const zcli_home = process.env.ZCLI_HOME; - if (fs.existsSync(in_pwd) || !zcli_home) { - return in_pwd; - } - if (!fs.existsSync(zcli_home) || !fs.lstatSync(zcli_home).isDirectory()) { - console.warn('$ZCLI_HOME is not pointing to a valid directory; ignoring...'); - return in_pwd; - } else { - return `${zcli_home}/${CONFIG_FILE}`; - } -} - -export function loadConfig(): Config { - const config_path = configLocation(); - if (!fs.existsSync(config_path)) { - return DEFAULT_CONFIG; - } - const unparsed = fs.readFileSync(config_path); - try { - const parsed = JSON.parse(unparsed.toString()); - assert(ALL_NETWORKS.includes(parsed.network)); - assert(!parsed.defaultWallet || parsed.wallets.hasOwnProperty(parsed.defaultWallet)); - return parsed; - } catch (err) { - console.warn('Invalid .zcli-config.json; ignoring...'); - return DEFAULT_CONFIG; - } -} - -export function saveConfig(config: Config) { - const config_path = configLocation(); - const config_string = JSON.stringify(config, null, 4); - fs.writeFileSync(config_path, config_string); -} diff --git a/infrastructure/zcli/src/index.ts b/infrastructure/zcli/src/index.ts deleted file mode 100644 index 48c0bc0a51..0000000000 --- a/infrastructure/zcli/src/index.ts +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env node - -import { Command } from 'commander'; -import * as commands from './commands'; -import { loadConfig } from './config'; -import type { Network } from './types'; - -function print(object: any) { - console.log(JSON.stringify(object, null, 4)); -} - -async function main() { - const config = loadConfig(); - const program = new Command(); - - const handler = async ( - operation: 'transfer' | 'deposit' | 'withdraw', - fast: boolean, - json?: string, - amount?: string, - token?: string, - recipient?: string, - fastProcessing?: boolean - ) => { - if (json && (amount || token || recipient)) { - throw new Error('--json option and positional arguments are mutually exclusive'); - } - if (!config.defaultWallet && !json) { - throw new Error('sender is not provided'); - } - if (operation == 'deposit' || operation == 'withdraw') { - recipient = recipient || config.defaultWallet || ''; - } - // prettier-ignore - const txDetails = json ? JSON.parse(json) : { - privkey: config.wallets[config.defaultWallet as any], - to: recipient, - amount, - token, - fastProcessing, - }; - const hash = await commands.submitTx(operation, txDetails, fast, program.network); - print(fast ? hash : await commands.txInfo(hash, program.network)); - }; - - program.version('0.1.0').name('zcli').option('-n, --network ', 'select network', config.network); - - program - .command('account [address]') - .description('view account info') - .action(async (address?: string) => { - if (!address && !config.defaultWallet) { - throw new Error('no address provided'); - } - address = address || config.defaultWallet || ''; - print(await commands.accountInfo(address, program.network)); - }); - - program - .command('transaction ') - .description('view transaction info') - .action(async (tx_hash: string) => { - print(await commands.txInfo(tx_hash, program.network)); - }); - - program - .command('transfer [amount] [token] [recipient]') - .description('make a transfer') - .option('--json ', 'supply transfer info as json string') - .option('--fast', 'do not wait for transaction commitment') - .action(async (amount, token, recipient, cmd) => { - await handler('transfer', cmd.fast, cmd.json, amount, token, recipient); - }); - - program - .command('deposit [amount] [token] [recipient]') - .description('make a deposit') - .option('--json ', 'supply deposit info as json string') - .option('--fast', 'do not wait for transaction commitment') - .action(async (amount, token, recipient, cmd) => { - await handler('deposit', cmd.fast, cmd.json, amount, token, recipient); - }); - - program - .command('withdraw [amount] [token] [recipient]') - .description('make a withdraw') - .option('--json ', 'supply transfer info as json string') - .option('--fast', 'do not wait for transaction commitment') - .option('--fastWithdrawal', 'requests block with tx to be executed as fast as possible') - .action(async (amount, token, recipient, cmd) => { - await handler('withdraw', cmd.fast, cmd.json, amount, token, recipient, cmd.fastWithdrawal); - }); - - program - .command('await ') - .description('await for transaction commitment/verification') - .option('-t, --timeout ', 'set a timeout', '60') - .action(async (type: string, tx_hash: string, cmd: Command) => { - if (type !== 'verify' && type !== 'commit') { - throw new Error('can only "await commit" or "await verify"'); - } - const timeout = Number.parseFloat(cmd.timeout) * 1000; - // prettier-ignore - print(await Promise.race([ - commands.txInfo(tx_hash, program.network, type.toUpperCase() as any), - new Promise((resolve) => setTimeout(resolve, timeout, null)) - ])); - }); - - const networks = new Command('networks'); - - networks - .description('view configured networks') - .action(async () => { - print(await commands.availableNetworks()); - }) - .command('default [network]') - .description('print or set default network') - .action((network?: Network) => { - print(commands.defaultNetwork(config, network)); - }); - - program.addCommand(networks); - - const wallets = new Command('wallets'); - - wallets - .description('view saved wallets') - .action(() => { - print(commands.listWallets(config)); - }) - .command('add [private_key]') - .description('create or import a wallet') - .action((privkey?: string) => { - console.warn('[WARNING]: private keys are stored unencrypted'); - print(commands.addWallet(config, privkey)); - }); - - wallets - .command('default [address]') - .description('print or set default wallet') - .action((address?: string) => { - print(commands.defaultWallet(config, address)); - }); - - wallets - .command('delete
') - .description('delete a wallet') - .action((address: string) => { - commands.removeWallet(config, address); - print(commands.listWallets(config)); - }); - - program.addCommand(wallets); - - await program.parseAsync(process.argv); -} - -main() - .then(() => process.exit(0)) - .catch((err: Error) => { - console.error('Error:', err.message); - process.exit(1); - }); diff --git a/infrastructure/zcli/src/types.ts b/infrastructure/zcli/src/types.ts deleted file mode 100644 index b8ccd5aeef..0000000000 --- a/infrastructure/zcli/src/types.ts +++ /dev/null @@ -1,45 +0,0 @@ -export type Network = 'localhost' | 'mainnet' | 'ropsten' | 'rinkeby'; - -export const ALL_NETWORKS: Network[] = ['localhost', 'mainnet', 'ropsten', 'rinkeby']; - -export interface Config { - network: Network; - defaultWallet: string | null; - wallets: { - // address -> privkey - [address: string]: string; - }; -} - -export interface AccountInfo { - address: string; - network: Network; - account_id?: number; - nonce: number; - balances: { - [token: string]: string; - }; -} - -export interface TxInfo { - network: Network; - transaction: null | { - status: 'error' | 'success'; - from: string; - to: string; - hash: string; - operation: string; - token?: string; - amount?: string; - fee?: string; - nonce: number; - }; -} - -export interface TxDetails { - privkey: string; - to: string; - token: string; - amount: string; - fastProcessing?: boolean; -} diff --git a/infrastructure/zcli/test/commands.test.ts b/infrastructure/zcli/test/commands.test.ts deleted file mode 100644 index 2c182b4df1..0000000000 --- a/infrastructure/zcli/test/commands.test.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { expect, use } from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import fs from 'fs'; -import * as path from 'path'; -import mock from 'mock-fs'; -import type { Network, Config } from '../src/types'; -import * as ethers from 'ethers'; -import * as zksync from 'zksync'; -import * as commands from '../src/commands'; -import { saveConfig, loadConfig, configLocation, DEFAULT_CONFIG } from '../src/config'; - -use(chaiAsPromised); - -const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, `etc/test_config/constant`); -const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: 'utf-8' })); - -describe('Fetching Information', () => { - let ethDepositor: string; - let alice: ethers.Wallet; - let bob: ethers.Wallet; - let deposit_hash: string; - let setkey_hash: string; - let transfer_hash: string; - - before('make some deposits & transactions', async () => { - let ethProvider; - if (process.env.CI == '1') { - ethProvider = new ethers.providers.JsonRpcProvider('http://geth:8545'); - } else { - ethProvider = new ethers.providers.JsonRpcProvider(); - } - const syncProvider = await zksync.getDefaultProvider('localhost', 'HTTP'); - const ethWallet = ethers.Wallet.fromMnemonic(ethTestConfig.test_mnemonic as string, "m/44'/60'/0'/0/0").connect( - ethProvider - ); - ethDepositor = ethWallet.address; - alice = ethers.Wallet.createRandom().connect(ethProvider); - bob = ethers.Wallet.createRandom(); - const [aliceWallet, syncWallet] = await Promise.all([ - zksync.Wallet.fromEthSigner(alice, syncProvider), - zksync.Wallet.fromEthSigner(ethWallet, syncProvider) - ]); - const ethDeposit = await syncWallet.depositToSyncFromEthereum({ - depositTo: alice.address, - token: 'ETH', - amount: ethers.utils.parseEther('1.5') - }); - const daiDeposit = await syncWallet.depositToSyncFromEthereum({ - depositTo: alice.address, - token: 'DAI', - amount: syncProvider.tokenSet.parseToken('DAI', '18.0'), - approveDepositAmountForERC20: true - }); - await Promise.all([ethDeposit.awaitReceipt(), daiDeposit.awaitReceipt()]); - const changePubkey = await aliceWallet.setSigningKey({ - feeToken: 'ETH', - ethAuthType: 'ECDSA' - }); - await changePubkey.awaitReceipt(); - const txHandle = await aliceWallet.syncTransfer({ - to: bob.address, - token: 'ETH', - amount: ethers.utils.parseEther('0.7') - }); - await txHandle.awaitReceipt(); - await syncProvider.disconnect(); - deposit_hash = daiDeposit.ethTx.hash; - setkey_hash = changePubkey.txHash; - transfer_hash = txHandle.txHash; - }); - - describe('Account Info', () => { - it('should fetch correct info', async () => { - const info = await commands.accountInfo(alice.address); - expect(info.address).to.equal(alice.address); - expect(info.network).to.equal('localhost'); - expect(info.nonce).to.equal(2); - expect(info.balances).to.have.property('DAI', '18.0'); - expect(info.balances.ETH).to.exist; - expect(+info.balances.ETH).to.be.within(0.77, 0.8); - expect(info.account_id).to.be.a('number'); - }); - - it('should fail on invalid network', () => { - const invalid_network = 'random' as Network; - expect(commands.accountInfo(alice.address, invalid_network)).to.be.rejected; - }); - - it('should be empty for non-existent account', async () => { - const non_existent = ethers.Wallet.createRandom().address; - const info = await commands.accountInfo(non_existent); - expect(info.balances).to.be.empty; - expect(info.account_id).to.be.null; - expect(info.nonce).to.equal(0); - }); - }); - - describe('Transaction Info', () => { - it('should fetch correct info - transfer', async () => { - const info = await commands.txInfo(transfer_hash); - const tx = info.transaction; - expect(info.network).to.equal('localhost'); - expect(tx?.status).to.equal('success'); - expect(tx?.hash).to.equal(transfer_hash); - expect(tx?.operation).to.equal('Transfer'); - expect(tx?.from).to.equal(alice.address.toLowerCase()); - expect(tx?.to).to.equal(bob.address.toLowerCase()); - expect(tx?.nonce).to.equal(1); - expect(tx?.token).to.equal('ETH'); - expect(tx?.amount).to.equal('0.7'); - expect(tx?.fee).to.exist; - }); - - it('should fetch correct info - setkey', async () => { - const info = await commands.txInfo(setkey_hash); - const tx = info.transaction; - expect(info.network).to.equal('localhost'); - expect(tx?.status).to.equal('success'); - expect(tx?.hash).to.equal(setkey_hash); - expect(tx?.operation).to.equal('ChangePubKey'); - expect(tx?.from).to.equal(alice.address.toLowerCase()); - expect(tx?.to).to.be.a('string'); - expect(tx?.nonce).to.equal(0); - expect(tx?.token).to.equal('ETH'); - expect(tx?.amount).to.not.exist; - expect(tx?.fee).to.exist; - }); - - it('should fetch correct info - deposit', async () => { - const info = await commands.txInfo(deposit_hash); - const tx = info.transaction; - expect(info.network).to.equal('localhost'); - expect(tx?.status).to.equal('success'); - expect(tx?.hash).to.equal(deposit_hash); - expect(tx?.operation).to.equal('Deposit'); - expect(tx?.from).to.equal(ethDepositor.toLowerCase()); - expect(tx?.to).to.equal(alice.address.toLowerCase()); - expect(tx?.nonce).to.equal(-1); - expect(tx?.token).to.equal('DAI'); - expect(tx?.amount).to.equal('18.0'); - expect(tx?.fee).to.not.exist; - }); - - it('should fail on invalid network', () => { - const invalid_network = 'random' as Network; - expect(commands.txInfo(transfer_hash, invalid_network)).to.be.rejected; - }); - - it('should be empty for non-existent transaction', async () => { - const non_existent = 'sync-tx:8888888888888888888888888888888888888888888888888888888888888888'; - const info = await commands.txInfo(non_existent); - expect(info.transaction).to.be.null; - }); - }); - - describe('Networks', () => { - it('should return available networks', async () => { - const networks = await commands.availableNetworks(); - expect(networks).to.be.an('array'); - expect(networks.length).to.be.within(1, 4); - expect(networks).to.include('localhost'); - }); - }); -}); - -describe('Config Management', () => { - const alice = ethers.Wallet.createRandom(); - const bob = ethers.Wallet.createRandom(); - const eve = ethers.Wallet.createRandom(); - const config1: Config = { - network: 'ropsten', - defaultWallet: alice.address.toLowerCase(), - wallets: { - [alice.address.toLowerCase()]: alice.privateKey, - [bob.address.toLowerCase()]: bob.privateKey - } - }; - const config2: Config = { - network: 'mainnet', - defaultWallet: eve.address.toLowerCase(), - wallets: { [eve.address.toLowerCase()]: eve.privateKey } - }; - - beforeEach('create mock fs', () => { - mock({ - '.zcli-config.json': JSON.stringify(config1), - 'customPath/.zcli-config.json': JSON.stringify(config1) - }); - }); - - afterEach('restore fs', () => { - mock.restore(); - }); - - it('should properly locate config file', () => { - expect(configLocation()).to.equal('./.zcli-config.json'); - process.env.ZCLI_HOME = 'customPath'; - expect(configLocation()).to.equal('./.zcli-config.json'); - fs.unlinkSync('./.zcli-config.json'); - expect(configLocation()).to.equal('customPath/.zcli-config.json'); - fs.unlinkSync('customPath/.zcli-config.json'); - expect(configLocation()).to.equal('customPath/.zcli-config.json'); - delete process.env.ZCLI_HOME; - expect(configLocation()).to.equal('./.zcli-config.json'); - }); - - it('should properly read config file', () => { - expect(loadConfig()).to.deep.equal(config1); - }); - - it('should create default config when needed', () => { - fs.unlinkSync('./.zcli-config.json'); - expect(loadConfig()).to.deep.equal(DEFAULT_CONFIG); - }); - - it('should properly save config file', () => { - saveConfig(config2); - expect(loadConfig()).to.deep.equal(config2); - }); - - describe('Networks', () => { - it('should properly get/set default network', () => { - const config = loadConfig(); - const invalid_network = 'random' as Network; - expect(commands.defaultNetwork(config)).to.equal(config1.network); - expect(commands.defaultNetwork(config, 'rinkeby')).to.equal('rinkeby').to.equal(config.network); - expect(() => commands.defaultNetwork(config, invalid_network)).to.throw(); - }); - }); - - describe('Wallets', () => { - it('should properly get/set default wallet', () => { - const config = loadConfig(); - const new_wallet = bob.address; - const invalid_wallet = '0x8888888888888888888888888888888888888888'; - expect(commands.defaultWallet(config)).to.equal(config1.defaultWallet); - expect(commands.defaultWallet(config, new_wallet)) - .to.equal(new_wallet.toLowerCase()) - .to.equal(config.defaultWallet); - expect(() => commands.defaultWallet(config, invalid_wallet)).to.throw(); - }); - - it('should properly add a wallet', () => { - const config = loadConfig(); - const wallet = ethers.Wallet.createRandom(); - expect(commands.addWallet(config, wallet.privateKey)).to.equal(wallet.address); - expect(config.wallets).to.have.property(wallet.address.toLowerCase(), wallet.privateKey); - }); - - it('should properly remove a wallet', () => { - const config = loadConfig(); - commands.removeWallet(config, alice.address); - expect(config.wallets).to.not.have.key(alice.address.toLowerCase()); - expect(config.defaultWallet).to.be.null; - }); - - it('should list all wallets', () => { - const config = loadConfig(); - const wallet_list = commands.listWallets(config); - expect(wallet_list).to.be.an('array').with.lengthOf(2); - expect(wallet_list).to.include(config.defaultWallet); - expect(wallet_list).to.include(bob.address.toLowerCase()); - }); - }); -}); - -describe('Making Transactions', () => { - const rich = ethers.Wallet.fromMnemonic(ethTestConfig.test_mnemonic as string, "m/44'/60'/0'/0/0"); - const poor1 = ethers.Wallet.createRandom(); - const poor2 = ethers.Wallet.createRandom(); - - it('should make a deposit - ETH', async () => { - const hash = await commands.deposit({ - to: poor1.address, - privkey: rich.privateKey, - token: 'ETH', - amount: '3.1415' - }); - const info = await commands.txInfo(hash); - expect(info).to.deep.equal({ - network: 'localhost', - transaction: { - status: 'success', - from: rich.address.toLowerCase(), - to: poor1.address.toLowerCase(), - hash, - operation: 'Deposit', - nonce: -1, - amount: '3.1415', - token: 'ETH' - } - }); - }); - - it('should make a deposit - DAI', async () => { - const hash = await commands.deposit({ - to: poor1.address, - privkey: rich.privateKey, - token: 'DAI', - amount: '2.7182' - }); - const info = await commands.txInfo(hash); - expect(info).to.deep.equal({ - network: 'localhost', - transaction: { - status: 'success', - from: rich.address.toLowerCase(), - to: poor1.address.toLowerCase(), - hash, - operation: 'Deposit', - nonce: -1, - amount: '2.7182', - token: 'DAI' - } - }); - }); - - it('should transfer tokens', async () => { - await commands.deposit({ - to: poor1.address, - privkey: rich.privateKey, - token: 'DAI', - amount: '1.4142' - }); - const hash = await commands.transfer({ - to: poor2.address, - privkey: poor1.privateKey, - token: 'DAI', - amount: '1.0' - }); - const info = await commands.txInfo(hash); - const tx = info.transaction; - expect(info.network).to.equal('localhost'); - expect(tx?.status).to.equal('success'); - expect(tx?.from).to.equal(poor1.address.toLowerCase()); - expect(tx?.to).to.equal(poor2.address.toLowerCase()); - expect(tx?.hash).to.equal(hash); - expect(tx?.operation).to.equal('Transfer'); - expect(tx?.amount).to.equal('1.0'); - expect(tx?.token).to.equal('DAI'); - expect(tx?.nonce).to.equal(1); - expect(tx?.fee).to.exist; - const account = await commands.accountInfo(poor2.address); - expect(account.address).to.equal(poor2.address); - expect(account.balances.DAI).to.equal('1.0'); - }); - - it('should fail if not enough tokens', () => { - // prettier-ignore - expect(commands.transfer({ - to: poor2.address, - privkey: poor1.privateKey, - token: 'MLTT', - amount: '73.0' - })).to.be.rejected; - }); - - it('should wait for commitment', async () => { - await commands.deposit({ - to: poor1.address, - privkey: rich.privateKey, - token: 'DAI', - amount: '1.5' - }); - const hash = await commands.transfer( - { - to: poor2.address, - privkey: poor1.privateKey, - token: 'DAI', - amount: '1.0' - }, - true - ); - const info = await commands.txInfo(hash, 'localhost', 'COMMIT'); - expect(info.transaction).to.not.be.null; - }); -}); diff --git a/infrastructure/zcli/tsconfig.json b/infrastructure/zcli/tsconfig.json deleted file mode 100644 index f96df8d60e..0000000000 --- a/infrastructure/zcli/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es2019", - "module": "commonjs", - "outDir": "build", - "strict": true, - "esModuleInterop": true, - "noEmitOnError": true, - "skipLibCheck": true, - "declaration": true - }, - "files": [ - "src/index.ts" - ] -} diff --git a/infrastructure/zk/src/test/integration.ts b/infrastructure/zk/src/test/integration.ts index b9f63eaf93..494d7f488c 100644 --- a/infrastructure/zk/src/test/integration.ts +++ b/infrastructure/zk/src/test/integration.ts @@ -96,7 +96,6 @@ export async function all() { await api(); await apiDocs(); await withdrawalHelpers(); - await zcli(); await rustSDK(); // have to kill server before running data-restore await utils.spawn('killall zksync_server'); @@ -117,10 +116,6 @@ export async function api() { await utils.spawn('yarn ts-tests api-test'); } -export async function zcli() { - await utils.spawn('yarn zcli test'); -} - export async function server() { await utils.spawn('yarn ts-tests test'); } @@ -208,14 +203,6 @@ command } }); -command - .command('zcli') - .description('run zcli integration tests') - .option('--with-server') - .action(async (cmd: Command) => { - cmd.withServer ? await withServer(zcli, 240) : await zcli(); - }); - command .command('server') .description('run server integration tests') diff --git a/package.json b/package.json index f64e4abc23..e1f1034d61 100644 --- a/package.json +++ b/package.json @@ -8,21 +8,11 @@ "sdk/zksync.js", "sdk/zksync-crypto", "contracts", - "infrastructure/zcli", - "infrastructure/explorer", "infrastructure/zk", "infrastructure/reading-tool", "infrastructure/token-lists-manager", "infrastructure/api-docs", "core/tests/ts-tests" - ], - "nohoist": [ - "**/@vue/**", - "**/vue-template-compiler/**", - "**/bootstrap-vue/**", - "**/@vue", - "**/vue-template-compiler", - "**/bootstrap-vue" ] }, "scripts": { @@ -32,7 +22,6 @@ "zksync": "yarn workspace zksync", "crypto": "yarn workspace zksync-crypto", "contracts": "yarn workspace franklin-contracts", - "zcli": "yarn workspace zcli", "ts-tests": "yarn workspace ts-tests", "explorer": "yarn workspace sync-explorer", "zk": "yarn workspace zk", diff --git a/yarn.lock b/yarn.lock index fe2351bddc..7391fb1906 100644 --- a/yarn.lock +++ b/yarn.lock @@ -923,13 +923,6 @@ dependencies: "@types/node" "*" -"@types/chai-as-promised@^7.1.3": - version "7.1.4" - resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz#caf64e76fb056b8c8ced4b761ed499272b737601" - integrity sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA== - dependencies: - "@types/chai" "*" - "@types/chai@*", "@types/chai@^4.2.14": version "4.2.21" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" @@ -965,11 +958,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/isomorphic-fetch@^0.0.35": - version "0.0.35" - resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.35.tgz#c1c0d402daac324582b6186b91f8905340ea3361" - integrity sha512-DaZNUvLDCAnCTjgwxgiL1eQdxIKEpNLOlTNtAgnZc50bG2copGhRrFN9/PxPBuJe+tZVLCbQ7ls0xveXVRPkvw== - "@types/json-schema@^7.0.7": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -1023,13 +1011,6 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== -"@types/mock-fs@^4.13.0": - version "4.13.1" - resolved "https://registry.yarnpkg.com/@types/mock-fs/-/mock-fs-4.13.1.tgz#9201554ceb23671badbfa8ac3f1fa9e0706305be" - integrity sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA== - dependencies: - "@types/node" "*" - "@types/node-fetch@^2.5.5", "@types/node-fetch@^2.5.7": version "2.5.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" @@ -5877,14 +5858,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isomorphic-fetch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" - integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== - dependencies: - node-fetch "^2.6.1" - whatwg-fetch "^3.4.1" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -7044,7 +7017,7 @@ mocha@^8.2.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" -mock-fs@^4.1.0, mock-fs@^4.13.0: +mock-fs@^4.1.0: version "4.14.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== @@ -10310,11 +10283,6 @@ whatwg-fetch@2.0.4: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== -whatwg-fetch@^3.4.1: - version "3.6.2" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== - whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -10650,7 +10618,7 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== "zksync@link:sdk/zksync.js": - version "0.12.0-alpha.4" + version "0.12.0-alpha.6" dependencies: axios "^0.21.2" websocket "^1.0.30" From 7fd970842b326abacc44add7a4cc4aa2cf5de395 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 22 Feb 2022 17:23:58 +0300 Subject: [PATCH 11/15] Add type definitions for chai-as-promised --- core/tests/ts-tests/package.json | 1 + yarn.lock | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/core/tests/ts-tests/package.json b/core/tests/ts-tests/package.json index 8581fb8fe7..dac5de2302 100644 --- a/core/tests/ts-tests/package.json +++ b/core/tests/ts-tests/package.json @@ -23,6 +23,7 @@ "@types/mocha-steps": "^1.3.0", "@types/node": "^14.14.5", "@types/node-fetch": "^2.5.7", + "@types/chai-as-promised": "^7.1.1", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "ethers": "^5.4.4", diff --git a/yarn.lock b/yarn.lock index 7391fb1906..6dcd13e37f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -923,6 +923,13 @@ dependencies: "@types/node" "*" +"@types/chai-as-promised@^7.1.1": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255" + integrity sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ== + dependencies: + "@types/chai" "*" + "@types/chai@*", "@types/chai@^4.2.14": version "4.2.21" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" From 3b9e2c3d2e2d774c10cd6e80f058e95b1ad85e2a Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 22 Feb 2022 17:51:16 +0300 Subject: [PATCH 12/15] Make token search case-insensitive --- .../zksync_api/src/utils/token_db_cache.rs | 6 +- core/lib/storage/sqlx-data.json | 110 +++++++++--------- core/lib/storage/src/tests/tokens.rs | 8 ++ core/lib/storage/src/tokens/mod.rs | 9 +- core/lib/types/src/tokens.rs | 44 ++++++- 5 files changed, 116 insertions(+), 61 deletions(-) diff --git a/core/bin/zksync_api/src/utils/token_db_cache.rs b/core/bin/zksync_api/src/utils/token_db_cache.rs index 3a494e356b..2d57fd5f56 100644 --- a/core/bin/zksync_api/src/utils/token_db_cache.rs +++ b/core/bin/zksync_api/src/utils/token_db_cache.rs @@ -22,6 +22,7 @@ impl TokenDBCache { Self::default() } + /// Performs case-insensitive token search. pub async fn get_token( &self, storage: &mut StorageProcessor<'_>, @@ -29,7 +30,8 @@ impl TokenDBCache { ) -> anyhow::Result> { let token_query = token_query.into(); // Just return token from cache. - if let Some((token, update_time)) = self.cache.read().await.get(&token_query) { + if let Some((token, update_time)) = self.cache.read().await.get(&token_query.to_lowercase()) + { if update_time.elapsed() < TOKEN_INVALIDATE_CACHE { return Ok(Some(token.clone())); } @@ -46,7 +48,7 @@ impl TokenDBCache { self.cache .write() .await - .insert(token_query, (token.clone(), Instant::now())); + .insert(token_query.to_lowercase(), (token.clone(), Instant::now())); } Ok(token) diff --git a/core/lib/storage/sqlx-data.json b/core/lib/storage/sqlx-data.json index 068565120e..8c7bc78beb 100644 --- a/core/lib/storage/sqlx-data.json +++ b/core/lib/storage/sqlx-data.json @@ -5584,6 +5584,61 @@ "nullable": [] } }, + "ba69c8315c69469b20ca6069708732c6ba2e3acee17dc3bde55622051746250c": { + "query": "\n SELECT id, address, decimals, kind as \"kind: _\", symbol FROM tokens\n WHERE lower(symbol) = lower($1)\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "address", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "decimals", + "type_info": "Int2" + }, + { + "ordinal": 3, + "name": "kind: _", + "type_info": { + "Custom": { + "name": "token_kind", + "kind": { + "Enum": [ + "ERC20", + "NFT", + "None" + ] + } + } + } + }, + { + "ordinal": 4, + "name": "symbol", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + } + }, "baaaff359564c5d1094fcf2650d53cf9dcac5d50fc3a549c6cff53dd472350f7": { "query": "\n SELECT * FROM ticker_price\n WHERE token_id = $1\n LIMIT 1\n ", "describe": { @@ -6283,61 +6338,6 @@ ] } }, - "c55673eb654deef9816a03d81ac34767003f366ef28a6be49ad3a9509f21aa1a": { - "query": "\n SELECT id, address, decimals, kind as \"kind: _\", symbol FROM tokens\n WHERE symbol = $1\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "address", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "decimals", - "type_info": "Int2" - }, - { - "ordinal": 3, - "name": "kind: _", - "type_info": { - "Custom": { - "name": "token_kind", - "kind": { - "Enum": [ - "ERC20", - "NFT", - "None" - ] - } - } - } - }, - { - "ordinal": 4, - "name": "symbol", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false - ] - } - }, "c7459e7624c46417d3a91fc39b05128cf3e88097ae114d8aad6e22b9b2cd84e9": { "query": "\n INSERT INTO accounts ( id, last_block, nonce, address, pubkey_hash )\n VALUES ( $1, $2, $3, $4, $5 )\n ", "describe": { diff --git a/core/lib/storage/src/tests/tokens.rs b/core/lib/storage/src/tests/tokens.rs index aa1a171546..9dd8f43fad 100644 --- a/core/lib/storage/src/tests/tokens.rs +++ b/core/lib/storage/src/tests/tokens.rs @@ -118,6 +118,14 @@ async fn tokens_storage(mut storage: StorageProcessor<'_>) -> QueryResult<()> { .expect("token by symbol not found"); assert_eq!(token_b, token_b_by_symbol); + // Try case-insensitive search + let token_b_by_symbol_case_insensitive = TokensSchema(&mut storage) + .get_token(TokenLike::Symbol(token_b.symbol.to_lowercase())) + .await + .expect("get token query failed") + .expect("token by symbol not found"); + assert_eq!(token_b, token_b_by_symbol_case_insensitive); + let db_nft_token = TokensSchema(&mut storage) .get_token(TokenLike::Id(nft.id)) .await diff --git a/core/lib/storage/src/tokens/mod.rs b/core/lib/storage/src/tokens/mod.rs index 04ebafd903..e7d846b14d 100644 --- a/core/lib/storage/src/tokens/mod.rs +++ b/core/lib/storage/src/tokens/mod.rs @@ -404,6 +404,7 @@ impl<'a, 'c> TokensSchema<'a, 'c> { /// Given the numeric token ID, symbol or address, returns token. pub async fn get_token(&mut self, token_like: TokenLike) -> QueryResult> { let start = Instant::now(); + let db_token = match token_like { TokenLike::Id(token_id) => { sqlx::query_as!( @@ -432,11 +433,17 @@ impl<'a, 'c> TokensSchema<'a, 'c> { .await? } TokenLike::Symbol(token_symbol) => { + // Note: for address and symbol queries we use `lower(...) = lower(...)` syntax, which means + // that we need to do a full scan over the table in order to achieve case-insensitive search. + // Luckily, we + // 1) don't have too much tokens. + // 2) most tokens requests will be handled by `TokenDbCache` anyway, + // so it shouldn't be a problem. sqlx::query_as!( DbToken, r#" SELECT id, address, decimals, kind as "kind: _", symbol FROM tokens - WHERE symbol = $1 + WHERE lower(symbol) = lower($1) LIMIT 1 "#, token_symbol diff --git a/core/lib/types/src/tokens.rs b/core/lib/types/src/tokens.rs index eacf7e9ad1..6be7fca570 100644 --- a/core/lib/types/src/tokens.rs +++ b/core/lib/types/src/tokens.rs @@ -85,9 +85,25 @@ impl TokenLike { /// Checks if the token is Ethereum. pub fn is_eth(&self) -> bool { match self { - TokenLike::Symbol(symbol) => symbol == "ETH", - TokenLike::Address(address) => *address == Address::zero(), - TokenLike::Id(id) => **id == 0, + Self::Symbol(symbol) => { + // Case-insensitive comparison against `ETH`. + symbol + .chars() + .map(|c| c.to_ascii_lowercase()) + .eq("eth".chars()) + } + Self::Address(address) => *address == Address::zero(), + Self::Id(id) => **id == 0, + } + } + + /// Makes request case-insensitive (lowercase). + /// Used to compare queries against keys in the cache. + pub fn to_lowercase(&self) -> Self { + match self { + Self::Id(id) => Self::Id(*id), + Self::Address(address) => Self::Address(*address), + Self::Symbol(symbol) => Self::Symbol(symbol.to_lowercase()), } } } @@ -368,4 +384,26 @@ mod tests { )) ); } + + #[test] + fn token_like_is_eth() { + let tokens = vec![ + TokenLike::Address(Address::zero()), + TokenLike::Id(TokenId(0)), + TokenLike::Symbol("ETH".into()), + TokenLike::Symbol("eth".into()), + ]; + + for token in tokens { + assert!(token.is_eth()); + } + } + + #[test] + fn token_like_to_case_insensitive() { + assert_eq!( + TokenLike::Symbol("ETH".into()).to_lowercase(), + TokenLike::Symbol("eth".into()) + ); + } } From 7126996272df1b0f5565232ac621ae169aa068d2 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 22 Feb 2022 18:01:42 +0300 Subject: [PATCH 13/15] Add an API test for case insensitivity --- core/tests/ts-tests/tests/api.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/tests/ts-tests/tests/api.test.ts b/core/tests/ts-tests/tests/api.test.ts index 0c80bf440f..e0f428ac77 100644 --- a/core/tests/ts-tests/tests/api.test.ts +++ b/core/tests/ts-tests/tests/api.test.ts @@ -233,6 +233,13 @@ describe('ZkSync REST API V0.2 tests', () => { const secondToken = await provider.tokenInfo(1); expect(tokens.list[0]).to.be.eql(firstToken); expect(tokens.list[1]).to.be.eql(secondToken); + + // Case insensitivity check. + const ethToken1 = await provider.tokenInfo('ETH'); + const ethToken2 = await provider.tokenInfo('eth'); + expect(tokens.list[0]).to.be.eql(ethToken1); + expect(tokens.list[0]).to.be.eql(ethToken2); + const firstTokenUSDPrice = await provider.getTokenPrice(0); const secondTokenUSDPrice = await provider.getTokenPrice(1); const expectedPrice = firstTokenUSDPrice / secondTokenUSDPrice; From 0071a6ad72ef3c8cb71965a96d7e34c84825d6b3 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 22 Feb 2022 20:05:56 +0300 Subject: [PATCH 14/15] Remove circuit.py --- docs/circuit.md | 156 ------------------ docs/circuit.py | 414 ------------------------------------------------ 2 files changed, 570 deletions(-) delete mode 100644 docs/circuit.md delete mode 100644 docs/circuit.py diff --git a/docs/circuit.md b/docs/circuit.md deleted file mode 100644 index 436c260b06..0000000000 --- a/docs/circuit.md +++ /dev/null @@ -1,156 +0,0 @@ -# ZKSync circuit - -Circuit describes R1CS, for further theoretical information look here: - - -## State structure - -- 2^24 accounts -- 2^10 balance leafs under each account: - -Full Merkle tree height: 34. - - - -([edit diagram here](https://docs.google.com/drawings/d/13bFjrSipx8-RKyAPbxzCCyXtswzvuFLjD-O8QEYaUYA/edit?usp=sharing)) - -Token type is determined by the index in the balance tree. - -## Circuit overview - -Currently our circuit is working over BN256 curve, its operations are implemented here: - As a gadget lib we use: The -only one public input to circuit is `public_data_commitment` which is the one from contract. - -What circuit basically does is cycles over `BLOCK_CHUNKS_SIZE` chunks and applies operation to state one by one (each -transaction, such as transfer, deposit, etc. consists of some amount of operations, see below). - -### Operation Processing - -Each operation is divided into chunks (corresponding to `pubdata_chunks`, number of chunks can be seen in the -[table](https://docs.google.com/spreadsheets/d/1ejK1MJfVehcwjgjVDFD3E2k1EZ7auqbG_y0DKidS9nA/edit?usp=drive_open&ouid=102923468016872611309)). -Each chunk has its own part of the public data (8 bytes) given through witness, together with needed `audit_pathes` and -other info to prove state. - -#### Chunk processing - -Operation processing does three main things: - -1. Aggregates public data provided in witness (and proves that this public data is corresponding to operation arguments - as described in the - [table](https://docs.google.com/spreadsheets/d/1ejK1MJfVehcwjgjVDFD3E2k1EZ7auqbG_y0DKidS9nA/edit?usp=drive_open&ouid=102923468016872611309)) -2. Signature is verified -3. State changing: During processing of one chunk we only update one leaf of the tree (which consists of calculating - `root_hash` before applying, proving it is equal to the previous chunk's `root_hash` and calculating `root_hash` - after applying). Current convention is that if operation changes state of only one leaf -- than it is done in the - first chunk, for `transfer` and `transfer_to_new` first operation updates sender's leaf, second operation updates - receiver's leaf, all other operations preserve state (`root_hash` is not changed). -4. Aggregating fees - -After all chunks are processed (which is the end of main cycle if you look into the code), we first add aggregated fees -to the account with id `validator_account_id`. Then all `public_data` aggregated during chunk processing is used to -calculate `public_data_commitment` and proving it is equal to public input. - -#### Public data commitment computation - -``` -h1 = sha256(block_number || operator_account_id) -h2 = sha256(h1 || previous_root) -h3 = sha256(h2 || new_root) -public_data_commitment = sha256(h3 || public_data) -``` - -This commitment ensures that in some block with `block_number` we made operations which are described in `public_data`, -fees are withheld to `operator_account_id` and this changed state in the way that `root_hash` of the whole merkle tree -migrated from `previous_root` to `new_root` - -## Pub data - -### Overview - -Spec for the exact bytes of `pub_data` for each operation is given in another document (TODO: actually create one -(ZKS-119)) Spec for the exact bytes of `signatures` for each is given in another document (TODO: actually create one -(ZKS-119)) - -Pub data is chosen in the way that you have all information needed to reconstruct state. - -### Structure - -Public data is represented by a byte string with concatenated pub data of the transaction. Each pub data set is prefixed -with a single byte of the type of operation. - -``` -|tx1_optype|tx1_pub_data|tx2_optype|tx2_pub_data|... -``` - -Public data of each operation is padded to the maximum pub data size of the circuit. - -The size of pub data members is indicated in bytes. Pubkeys in pub data are represented by Pedersen hashes truncated to -160 bits. - -### By operation - -([See table here](https://docs.google.com/spreadsheets/d/1ejK1MJfVehcwjgjVDFD3E2k1EZ7auqbG_y0DKidS9nA/edit?usp=drive_open&ouid=102923468016872611309)) - -## Main circuit ops - -Each operation in the circuit requires: - -- 2 Merkle path proof checks of height 34 -- 1 signature check - -### Circuit operations - -#### noop (optype = 0) - -Phony operation for block padding. - -Comments: - -- Optype must equal 0 so that padding can efficiently be added to the pub data before hashing in the smart contract. - -#### deposit - -Create an account and deposit a balance into it. - -Verification: - -- User initiates a deposit by a transaction on the root chain which creates a deposit queue entry -- Public data for the transaction is checked by the smart contract against the deposit queue - -#### transfer_to_new - -Create a new account and transfer some balance into it from an existing one. - -Verification: - -- Owner of existing account signs (optype, account, token, nonce, amount, fee, pubkey_hash) - -Comments: - -- Splitting into `transfer_to_new` and `deposit_from` operations is necessary because for each operation in this circuit - we only update one account/balance leaf in the tree. - -#### withdraw - -Withdraw part of a particular token balance to the mainchain. - -Verification: - -- Account owner signs (optype, account, token, leaf_nonce, amount, fee) - -#### transfer - -Transfer an amount of tokens from one account balance to another. - -| Pub data | Total size | -| ----------------------------------------------------------- | ---------- | -| amount: 3, token: 1, from_account: 3, to_account: 3, fee: 1 | 11 bytes | - -Verification: - -- Account owner signs (optype, from_account, to_account, token, amount, fee, nonce) - -## Todo / Questions - -- verify whether musig_pedersen is valid signature that we can use instead of musig_sha256 diff --git a/docs/circuit.py b/docs/circuit.py deleted file mode 100644 index c274f15393..0000000000 --- a/docs/circuit.py +++ /dev/null @@ -1,414 +0,0 @@ -# Citcuit pseudocode - -# Data structures - -struct op: - - # operation data - tx_type: # type of transaction, see the list: https://docs.google.com/spreadsheets/d/1ejK1MJfVehcwjgjVDFD3E2k1EZ7auqbG_y0DKidS9nA/edit#gid=0 - chunk: # op chunk number (0..3) - pubdata_chunk: # current chunk of the pubdata (always 8 bytes) - args: # arguments for the operation - - # Merkle branches - lhs: # left Merkle branch data - rhs: # right Merkle branch data - clear_account: # bool: instruction to clear the account in the current branch - clear_subaccount: # bool: instruction to clear the subaccount in the current branch - - # precomputed witness: - a: # depends on the optype, used for range checks - b: # depends on the optype, used for range checks - new_root: # new state root after the operation is applied - account_path: # Merkle path witness for the account in the current branch - subtree_path: # Merkle path witness for the subtree in the current branch - -struct cur: # current Merkle branch data - -struct computed: - last_chunk: # bool: whether the current chunk is the last one in sequence - pubdata: # pubdata accumulated over all chunks - range_checked: # bool: ensures that a >= b - new_pubkey_hash: # hash of the new pubkey, truncated to 20 bytes (used only for deposits) - - -# Circuit functions - -def circuit: - - running_hash := initial_hash - current_root := last_state_root - - prev.lhs := { 0, ... } - prev.rhs := { 0, ... } - prev.chunk := 0 - prev.new_root := 0 - - for op in operations: - - # enfore correct bitlentgh for every input in witness - # TODO: create a macro gadget to recursively iterate over struct member annotations (ZKS-119). - for x in op: - verify_bitlength(x) - - # check and prepare data - verify_correct_chunking(op, computed) - accumulate_sha256(op.pubdata_chunk) - accumulate_pubdata(op, computed) - - # prepare Merkle branch - cur := select_branch(op, computed) - cur.cosigner_pubkey_hash := hash(cur.cosigner_pubkey) - - # check initial Merkle paths, before applying the operation - op.clear_account := False - op.clear_subaccount := False - state_root := check_account_data(op, cur, computed, check_intersection = False) - enforce state_root == current_root - - # check validity and perform state updates for the current branch by modifying `cur` struct - execute_op(op, cur, computed) - - # check final Merkle paths after applying the operation - new_root := check_account_data(op, cur, computed, check_intersection = True) - - # NOTE: this is checked separately for each branch side, and we already enforced - # that `op.new_root` remains unchanged for both by enforcing that it is shared by all chunks - enforce new_root == op.new_root - - # update global state root on the last op chunk - if computed.last_chunk: - current_root = new_root - - # update `prev` references - # TODO: need a gadget to copy struct members one by one (ZKS-119). - prev.rhs = op.rhs - prev.lhs = op.lhs - prev.args = op.args - prev.new_root = op.new_root - prev.chunk = op.chunk - - # final checks after the loop end - enforce current_root == new_state_root - enforce running_hash == pubdata_hash - enforce last_chunk # any operation should close with the last chunk - - -# make sure that operation chunks are passed correctly -def verify_correct_chunking(op, computed): - - # enforce chunk sequence correctness - enforce (op.chunk == 0) or (op.chunk == prev.chunk + 1) # ensure that chunks come in sequence - max_chunks := switch op.tx_type - deposit => 4, - transfer_to_new=> 1, - transfer => 2, - # ...and so on - enforce op.chunk < max_chunks # 4 constraints - computed.last_chunk = op.chunk == max_chunks-1 # flag to mark the last op chunk - - # enforce that all chunks share the same witness: - # - `op.args` for the common arguments of the operation - # - `op.lhs` and `op.rhs` for left and right Merkle branches - # - `new_root` of the state after the operation is applied - correct_inputs := - op.chunk == 0 # skip check for the first chunk - or ( - prev.args == op.args and - prev.lhs == op.lhs and - prev.rhs == op.rhs and - prev.new_root == op.new_root - ) # TODO: need a gadget for logical equality which works with structs (ZKS-119). - - enforce correct_inputs - - -# accumulate pubdata from multiple chunks -def accumulate_pubdata(op, computed): - computed.pubdata = - if op.chunk == 0: - op.pubdata_chunk # initialize from the first chunk - else: - computed.pubdata << 8 + op.pubdata_chunk - - -# determine the Merkle branch side (0 for LHS, 1 for RHS) and set `cur` for the current Merkle branch -def select_branch(op, computed): - - op.current_side := LHS if op.tx_type == 'deposit' else op.chunk - - # TODO: need a gadget for conditional swap applied to each struct member (ZKS-119). - cur := op.lhs if current_side == LHS else op.rhs - - return cur - -def check_account_data(op, cur, computed, check_intersection): - - # leaf data for account and balance leaves - subaccount_data := ( - cur.subaccount_balance, - cur.subaccount_nonce, - cur.creation_nonce, - cur.cosigner_pubkey_hash, - cur.cosigner_balance, - cur.subaccount_token) - balance_data := cur.balance - - # subaccount emptiness check and clearing - cur.subaccount_is_empty := subaccount_data == EMPTY_SUBACCOUNT - subaccount_data = EMPTY_SUBACCOUNT if clear_subaccount else subaccount_data - - # subtree Merkle checks - balances_root := merkle_root(token, op.balances_path, balance_data) - subaccounts_root := merkle_root(token, op.balances_path, subaccount_data) - subtree_root := hash(balances_root, subaccounts_root) - - # account data - account_data := hash(cur.owner_pub_key, cur.subtree_root, cur.account_nonce) - - # account emptiness check and clearing - cur.account_is_empty := account_data == EMPTY_ACCOUNT - account_data = EMPTY_ACCOUNT if clear_account else account_data - - # final state Merkle root verification with conditional intersection check - intersection_path := intersection(op.account_path, cur.account, lhs.account, rhs.account, - lhs.intersection_hash, rhs.intersection_hash) - path_witness := intersection_path if check_intersection else op.account_path - state_root := merkle_root(cur.account, path_witness, account_data) - - return state_root - - -# verify operation and execute state updates -def execute_op(op, cur, computed): - - # universal range check - - computed.range_checked := op.a >= op.b - - # unpack floating point values and hashes - - op.args.amount := unpack(op.args.amount_packed) - op.args.fee := unpack(op.args.fee_packed) - - # some operations require tighter amount packing (with less precision) - - computed.compact_amount_correct := op.args.amount == op.args.compact_amount * 256 - - # new pubkey hash for deposits - - computed.new_pubkey_hash := hash(cur.new_pubkey) - - # signature check - - # NOTE: signature check must always be valid, but msg and signer can be phony - enforce check_sig(cur.sig_msg, cur.signer_pubkey) - - # execute operations - - op_valid := False - - op_valid = op_valid or op.tx_type == 'noop' - op_valid = op_valid or transfer_to_new(op, cur, computed) - op_valid = op_valid or deposit(op, cur, computed) - op_valid = op_valid or close_account(op, cur, computed) - op_valid = op_valid or withdraw(op, cur, computed) - op_valid = op_valid or escalation(op, cur, computed) - - op_valid = op_valid or create_subaccount(op, cur, computed) - op_valid = op_valid or close_subaccount(op, cur, computed) - op_valid = op_valid or fill_orders(op, cur, computed) - - # `op` MUST be one of the operations and MUST be valid - - enforce op_valid - - -def transfer_to_new(op, cur, computed): - # transfer_to_new validation is split into lhs and rhs; pubdata is combined from both branches - - lhs_valid := - op.tx_type == 'transfer_to_new' - - # here we process the first chunk - and op.chunk == 0 - - # sender authorized spending and recepient - and lhs.sig_msg == hash('transfer_to_new', lhs.account, lhs.token, lhs.account_nonce, op.args.amount_packed, - op.args.fee_packed, cur.new_pubkey) - - # sender is account owner - and lhs.signer_pubkey == cur.owner_pub_key - - # sender has enough balance: we checked above that `op.a >= op.b` - # NOTE: no need to check overflow for `amount + fee` because their bitlengths are enforced] - and computed.range_checked and (op.a == cur.balance) and (op.b == (op.args.amount + op.args.fee) ) - - # NOTE: updating the state is done by modifying data in the `cur` branch - if lhs_valid: - cur.leaf_balance = cur.leaf_balance - (op.args.amount + op.args.fee) - cur.account_nonce = cur.account_nonce + 1 - - rhs_valid := - op.tx_type == 'transfer_to_new' - - # here we process the second (last) chunk - and op.chunk == 1 - - # compact amount is passed to pubdata for this operation - and computed.compact_amount_correct - - # pubdata contains correct data from both branches, so we verify it agains `lhs` and `rhs` - and pubdata == (op.tx_type, lhs.account, lhs.token, lhs.compact_amount, cur.new_pubkey_hash, rhs.account, rhs.fee) - - # new account branch is empty - and cur.account_is_empty - - # sender signed the same recepient pubkey of which the hash was passed to public data - and lhs.new_pubkey == rhs.new_pubkey - - if rhs_valid: - cur.leaf_balance = op.args.amount - - return lhs_valid or rhs_valid - - -def deposit(op, cur, computed): - - ignore_pubdata := not last_chunk - tx_valid := - op.tx_type == 'deposit' - and (ignore_pubdata or pubdata == (cur.account, cur.token, args.compact_amount, cur.new_pubkey_hash, args.fee)) - and cur.is_account_empty - and computed.compact_amount_correct - and computed.range_checked and (op.a == op.args.amount) and (op.b == op.args.fee) - - if tx_valid: - cur.leaf_balance = op.args.amount - op.args.fee - - return tx_valid - -def close_account(op, cur, computed): - - tx_valid := - op.tx_type == 'close_account' - and pubdata == (cur.account, cur.subtree_root) - and cur.sig_msg == ('close_account', cur.account, cur.leaf_index, cur.account_nonce, cur.amount, cur.fee) - and cur.signer_pubkey == cur.owner_pub_key - - if tx_valid: - op.clear_account = True - - return tx_valid - -def no_nonce_overflow(nonce): - nonce_overflow := cur.leaf_nonce == 0x10000-1 # nonce is 2 bytes long - return not nonce_overflow - -def withdraw(op, cur, computed): - - tx_valid := - op.tx_type == 'withdraw' - and computed.compact_amount_correct - and pubdata == (op.tx_type, cur.account, cur.token, op.args.amount, op.args.fee) - and computed.range_checked and (op.a == cur.balance) and (op.b == (op.args.amount + op.args.fee) ) - and cur.sig_msg == ('withdraw', cur.account, cur.token, cur.account_nonce, cur.amount, cur.fee) - and cur.signer_pubkey == cur.owner_pub_key - and no_nonce_overflow(cur.leaf_nonce) - - if tx_valid: - cur.balance = cur.balance - (op.args.amount + op.args.fee) - cur.account_nonce = cur.leaf_nonce + 1 - - return tx_valid - - -def escalation(op, cur, computed): - - tx_valid := - op.tx_type == 'escalation' - and pubdata == (op.tx_type, cur.account, cur.subaccount, cur.creation_nonce, cur.leaf_nonce) - and cur.sig_msg == ('escalation', cur.account, cur.subaccount, cur.creation_nonce) - (cur.signer_pubkey == cur.owner_pub_key or cur.signer_pubkey == cosigner_pubkey) - - if tx_valid: - cur.clear_subaccount = True - - return tx_valid - -def transfer(op, cur, computed): - - lhs_valid := - op.tx_type == 'transfer' - and op.chunk == 0 - and lhs.sig_msg == ('transfer', lhs.account, lhs.token, lhs.account_nonce, op.args.amount_packed, - op.args.fee_packed, rhs.account_pubkey) - and lhs.signer_pubkey == cur.owner_pub_key - and computed.range_checked and (op.a == cur.balance) and (op.b == (op.args.amount + op.args.fee) ) - and no_nonce_overflow(cur.account_nonce) - - if lhs_valid: - cur.balance = cur.balance - (op.args.amount + op.args.fee) - cur.account_nonce = cur.account_nonce + 1 - - rhs_valid := - op.tx_type == 'transfer' - and op.chunk == 1 - and not cur.account_is_empty - and pubdata == (op.tx_type, lhs.account, lhs.token, op.args.amount, rhs.account, op.args.fee) - and computed.range_checked and (op.a == (cur.balance + op.args.amount) ) and (op.b == cur.balance ) - - if rhs_valid: - cur.balance = cur.balance + op.args.amount - - return lhs_valid or rhs_valid - -# Subaccount operations - -def create_subaccount(op, cur, computed): - - # On the LHS we have cosigner, we only use it for a overflow check - - lhs_valid: = - op.tx_type == 'create_subaccount' - and op.chunk == 0 - and computed.range_checked and (op.a == rhs.balance) and (op.b == (op.args.amount + op.args.fee) ) - - # We process everything else on the RHS - - rhs_valid := - op.tx_type == 'create_subaccount' - and op.chunk == 1 - and cur.sig_msg == ( - 'create_subaccount', - cur.account, # cur = rhs - lhs.account, # co-signer account on the lhs - cur.token, - cur.account_nonce, - op.args.amount_packed, - op.args.fee_packed ) - - and cur.signer_pubkey == cur.owner_pub_key - and cur.subaccount_is_empty - and pubdata == (op.tx_type, lhs.account, lhs.leaf_index, op.args.amount, rhs.account, op.args.fee) - and computed.range_checked and (op.a == (cur.subaccount_balance + op.args.amount) ) and (op.b == cur.subaccount_balance) - and no_nonce_overflow(cur.account_nonce) - - if rhs_valid: - # initialize subaccount - cur.subaccount_balance = cur.subaccount_balance + op.args.amount - cur.creation_nonce = cur.account_nonce - cur.cosigner_pubkey = lhs.account_pubkey - cur.subaccount_token = cur.token - - # update main account - cur.balance = cur.balance - (op.args.amount + op.args.fee) - cur.account_nonce = cur.account_nonce + 1 - - return lhs_valid or rhs_valid - -def close_subaccount(op, cur, computed): - # tbd: similar to create_subaccount() - -def fill_orders(op, cur, computed): - # tbd From 25911910ff73cfe538ba12f282fc62166dad5063 Mon Sep 17 00:00:00 2001 From: Peter Shilo Date: Tue, 22 Feb 2022 18:36:36 +0100 Subject: [PATCH 15/15] Removed workflow call --- .github/workflows/deploy-stage.yml | 38 ++++++++++++--- .github/workflows/template-docker-build.yml | 51 --------------------- 2 files changed, 32 insertions(+), 57 deletions(-) delete mode 100644 .github/workflows/template-docker-build.yml diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 03985527b3..a2433e3f65 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -25,12 +25,38 @@ jobs: build-images: name: Build and Push Docker Images needs: [setup] - uses: ./.github/workflows/template-docker-build.yml - secrets: - DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - with: - image_tag: ${{ needs.setup.outputs.image_tag }} + runs-on: [self-hosted, ci-runner] + + steps: + - uses: actions/checkout@v2 + - name: setup-env + run: | + echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV + echo CI=1 >> $GITHUB_ENV + echo $(pwd)/bin >> $GITHUB_PATH + + - name: start-services + run: | + docker-compose -f docker-compose-runner.yml down + docker-compose -f docker-compose-runner.yml up -d zk postgres + + - name: init + run: | + ci_run zk + ci_run zk run yarn + ci_run cp etc/tokens/{test,localhost}.json + ci_run zk run verify-keys unpack + ci_run zk db basic-setup + + - name: update-images + run: | + ci_run docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }} + ci_run zk docker push rust + + - name: docker-down + if: always() + run: | + docker-compose -f docker-compose-runner.yml down # Reminder: when disabling the deploy stage - comment the whole job out! deploy: diff --git a/.github/workflows/template-docker-build.yml b/.github/workflows/template-docker-build.yml deleted file mode 100644 index d07b44ec9c..0000000000 --- a/.github/workflows/template-docker-build.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build docker image -on: - workflow_call: - secrets: - DOCKERHUB_USER: - description: 'DOCKERHUB_USER' - required: true - DOCKERHUB_TOKEN: - description: 'DOCKERHUB_TOKEN' - required: true - inputs: - image_tag: - description: 'Tag of a built image to deploy' - type: string - required: true - -jobs: - build-images: - name: Build and Push Docker Images - env: - image_tag: ${{ inputs.image_tag }} - runs-on: [self-hosted, ci-runner] - steps: - - uses: actions/checkout@v2 - - name: setup-env - run: | - echo ZKSYNC_HOME=$(pwd) >> $GITHUB_ENV - echo CI=1 >> $GITHUB_ENV - echo $(pwd)/bin >> $GITHUB_PATH - - - name: start-services - run: | - docker-compose -f docker-compose-runner.yml up -d zk postgres - - - name: init - run: | - ci_run zk - ci_run zk run yarn - ci_run cp etc/tokens/{test,localhost}.json - ci_run zk run verify-keys unpack - ci_run zk db basic-setup - - - name: update-images - run: | - ci_run docker login -u ${{ secrets.DOCKERHUB_USER }} -p ${{ secrets.DOCKERHUB_TOKEN }} - ci_run zk docker push server - - - name: docker-down - if: always() - run: | - docker-compose -f docker-compose-runner.yml down