├── .cargo └── config.toml ├── .develatus-apparatus.mjs ├── .dockerignore ├── .env.example ├── .github └── workflows │ ├── autogen.yml │ ├── circuit-test.yml │ ├── coverage.yml │ ├── docker.sh │ ├── docker.yml │ ├── github-ops.yml │ ├── lint.yml │ ├── pi-eth-transfer.yml │ ├── pi-native-withdraw.yml │ ├── super-eth-transfer.yml │ ├── super-native-withdraw.yml │ ├── super-worst-case-keccak-0-32.yml │ ├── super-worst-case-mload.yml │ └── super-worst-case-smod.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── README.original.md ├── common ├── Cargo.toml └── src │ ├── json_rpc.rs │ ├── lib.rs │ └── prover.rs ├── contracts ├── Multicall.sol ├── Proxy.sol ├── ZkEvmBridgeEvents.sol ├── ZkEvmL1Bridge.sol ├── ZkEvmL2MessageDeliverer.sol ├── ZkEvmL2MessageDispatcher.sol ├── ZkEvmMagicNumbers.sol ├── ZkEvmMessageDelivererBase.sol ├── ZkEvmMessageDispatcherBase.sol ├── ZkEvmStorage.sol ├── ZkEvmUtils.sol ├── generated │ ├── .gitignore │ └── CircuitConfig.sol ├── interfaces │ ├── ICrossDomainMessenger.sol │ ├── IZkEvmMessageDelivererBase.sol │ ├── IZkEvmMessageDelivererWithProof.sol │ └── IZkEvmMessageDispatcher.sol ├── optimism │ ├── L1OptimismBridge.sol │ ├── L2OptimismBridge.sol │ └── OptimismWrapper.sol ├── templates │ ├── CommonBlockOperations.sol │ ├── HeaderUtil.sol │ ├── PatriciaAccountValidator.sol │ ├── PatriciaStorageValidator.sol │ ├── PublicInput.sol │ ├── mpt.yul │ ├── rlp.yul │ └── utils.yul └── tests │ └── ZkEvmTest.sol ├── coordinator ├── .gitignore ├── Cargo.toml ├── src │ ├── bin │ │ └── coordinator.rs │ ├── config.rs │ ├── faucet.rs │ ├── lib.rs │ ├── macros.rs │ ├── shared_state.rs │ ├── structs.rs │ └── utils.rs └── tests │ ├── chain.rs │ ├── commitment │ └── mod.rs │ ├── common │ └── mod.rs │ ├── deploy.rs │ ├── drop_message.rs │ ├── hop.rs │ ├── misc.rs │ ├── native_deposit.rs │ ├── native_withdraw.rs │ ├── patricia.rs │ ├── patricia │ ├── test-data-0000000000000000000000000000000000020000.json │ ├── test-data-0000000000000000000000000000000000030000.json │ ├── test-data-68b3465833fb72a70ecdf485e0e4c7bd8665fc45.json │ └── test-data-a5409ec958c83c3f309868babaca7c86dcb077c1.json │ └── worst_case.rs ├── dev ├── Cargo.toml ├── src │ ├── bytecode.rs │ ├── genesis.rs │ └── lib.rs └── tests │ └── autogen.rs ├── docker-compose-perf.yml ├── docker-compose-pub.yml ├── docker-compose.yml ├── docker ├── coordinator │ └── Dockerfile ├── dev │ └── Dockerfile ├── geth │ ├── Dockerfile │ ├── init.sh │ └── templates │ │ ├── l1-testnet.json │ │ └── l2-testnet.json ├── prover │ └── Dockerfile └── web │ ├── Dockerfile │ └── nginx.conf ├── docs └── README.md ├── prover ├── Cargo.toml ├── build.rs ├── src │ ├── bin │ │ ├── gen_params.rs │ │ ├── prover_cmd.rs │ │ └── prover_rpcd.rs │ ├── circuit_autogen.rs │ ├── circuit_witness.rs │ ├── circuits.rs │ ├── lib.rs │ ├── server.rs │ ├── shared_state.rs │ └── utils.rs └── tests │ ├── autogen.rs │ ├── proverd.rs │ └── verifier.rs ├── rust-toolchain ├── rustfmt.toml └── scripts ├── autogen.sh ├── ci_autogen_commit.sh ├── ci_commit_errors.sh ├── ci_grafana.sh ├── compile_contracts.sh ├── dev.sh ├── get_block_fixtures.sh ├── heavy_ci.sh ├── lint.sh ├── patch_genesis.mjs ├── purge_chain.sh ├── rpc_prover.sh ├── test_coverage.sh └── test_prover.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os="macos")'] 2 | rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security"] -------------------------------------------------------------------------------- /.develatus-apparatus.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | artifactsPath: 'build/contracts', 3 | proxyPort: 8545, 4 | rpcUrl: process.env.RPC, 5 | fuzzyMatchFactor: 0.8, 6 | ignore: /(mocks|tests|interfaces)\/.*\.sol/, 7 | } 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !common/ 3 | !coordinator/ 4 | !prover/ 5 | !dev/ 6 | !docker/ 7 | !Cargo.lock 8 | !Cargo.toml 9 | !rust-toolchain 10 | !.git/ 11 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MINER_PRIV_KEY=2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200 2 | MINER_ADDRESS=df08f82de32b8d460adbe8d72043e3a7e25a3b39 3 | -------------------------------------------------------------------------------- /.github/workflows/autogen.yml: -------------------------------------------------------------------------------- 1 | name: Autogen 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled, ready_for_review] 6 | push: 7 | branches: 8 | - main 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | cache: 17 | if: github.event.pull_request.draft == false 18 | timeout-minutes: 5 19 | runs-on: ubuntu-latest 20 | outputs: 21 | cache-hit: ${{ steps.cache-restore.outputs.cache-hit }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - id: cache-restore 26 | uses: actions/cache/restore@v3 27 | with: 28 | path: Cargo.lock 29 | key: ${{ github.workflow }}-${{ hashFiles('**/Cargo.lock', '**/*autogen*') }} 30 | enableCrossOsArchive: true 31 | 32 | autogen: 33 | needs: [cache] 34 | if: > 35 | github.event_name == 'workflow_dispatch' 36 | || contains(github.event.pull_request.labels.*.name, 'allow-autogen') 37 | || needs.cache.outputs.cache-hit != 'true' 38 | timeout-minutes: 7200 39 | runs-on: ["${{github.run_id}}", self-hosted, r6a.24xlarge] 40 | steps: 41 | - uses: actions/checkout@v3 42 | with: 43 | ref: ${{ github.head_ref }} 44 | fetch-depth: 0 45 | 46 | - name: Setup 47 | run: cp .env.example .env 48 | 49 | - name: Build docker images 50 | run: | 51 | docker buildx create --name mybuilder --use || echo 'skip' 52 | docker compose build --progress plain dev 53 | 54 | - name: Circuit config 55 | run: | 56 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c './scripts/autogen.sh autogen_circuit_config' 57 | 58 | - name: Cargo fmt 59 | run: | 60 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'cargo fmt --all' 61 | 62 | - name: EVM & Aggregation verifiers 63 | run: | 64 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c './scripts/autogen.sh autogen_verifier_' 65 | 66 | - name: Contracts 67 | run: | 68 | docker compose run --use-aliases --no-TTY --rm --entrypoint bash dev -c './scripts/compile_contracts.sh' 69 | 70 | - name: Patch genesis templates 71 | run: docker run --rm -v $(pwd):/host -w /host node:lts-alpine scripts/patch_genesis.mjs 72 | 73 | - name: Commit 74 | run: ./scripts/ci_autogen_commit.sh 75 | 76 | - id: cache-save 77 | uses: actions/cache/save@v3 78 | with: 79 | path: Cargo.lock 80 | key: ${{ github.workflow }}-${{ hashFiles('**/Cargo.lock', '**/*autogen*') }} 81 | enableCrossOsArchive: true 82 | -------------------------------------------------------------------------------- /.github/workflows/circuit-test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | circuit: 5 | required: true 6 | type: string 7 | 8 | test-name: 9 | required: true 10 | type: string 11 | 12 | instance-type: 13 | required: true 14 | type: string 15 | 16 | concurrency-group: 17 | required: true 18 | type: string 19 | 20 | jobs: 21 | test: 22 | name: ${{ inputs.circuit }} circuit aggregation ${{ inputs.test-name}} 23 | timeout-minutes: 7200 24 | concurrency: 25 | group: ${{ inputs.concurrency-group }} 26 | cancel-in-progress: true 27 | runs-on: ["${{github.run_id}}", self-hosted, "${{inputs.instance-type}}"] 28 | steps: 29 | - uses: actions/checkout@v3 30 | 31 | - name: Setup 32 | run: cp .env.example .env 33 | 34 | - name: Build docker images 35 | run: | 36 | docker compose down -v --remove-orphans || true 37 | docker buildx create --name mybuilder --use || echo 'skip' 38 | docker compose build --progress plain bootnode dev 39 | 40 | - name: Run test 41 | if: always() 42 | run: | 43 | docker compose -f docker-compose.yml -f docker-compose-perf.yml run --use-aliases --no-TTY --rm --entrypoint bash dev -c 'COORDINATOR_AGGREGATE_PROOF=true COORDINATOR_CIRCUIT_NAME=${{ inputs.circuit }} ./scripts/heavy_ci.sh ${{ inputs.test-name }}' 44 | ./scripts/ci_commit_errors.sh ag-${{ inputs.circuit }}-${{ inputs.test-name }} 45 | 46 | - name: Upload statistics 47 | if: always() 48 | env: 49 | GH_OPS_PAT: ${{ secrets.PAT_OPS_TRIGGER }} 50 | TEST_ID: ag-${{ inputs.circuit }}-${{ inputs.test-name }} 51 | TEST_NAME: ${{ inputs.test-name }} 52 | run: | 53 | ./scripts/ci_grafana.sh > grafana.sql 54 | payload=$(printf '%s' '{"ref":"master","inputs":{"dbname":"benchmarks"}}' | jq -c --arg sql "$(cat grafana.sql)" '.inputs.sql=$sql') 55 | curl --fail -H "authorization: token $GH_OPS_PAT" 'https://api.github.com/repos/privacy-scaling-explorations/github-ops/actions/workflows/pse-sql.yml/dispatches' -d "${payload}" 56 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: 3 | merge_group: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | types: [synchronize, opened, reopened, ready_for_review] 10 | 11 | jobs: 12 | main: 13 | if: github.event.pull_request.draft == false 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 16 | cancel-in-progress: true 17 | runs-on: ["${{github.run_id}}", self-hosted, r6a.2xlarge] 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | ref: ${{ (github.event.pull_request && format('refs/pull/{0}/merge', github.event.pull_request.number)) || github.ref }} 22 | 23 | - name: Setup 24 | run: cp .env.example .env 25 | 26 | - name: Build docker images 27 | run: | 28 | docker compose down -v --remove-orphans || true 29 | docker buildx create --name mybuilder --use || echo 'skip' 30 | docker compose build --progress plain bootnode dev 31 | 32 | - name: Compile Contracts 33 | run: | 34 | docker compose run --no-TTY --rm --entrypoint bash dev -c './scripts/compile_contracts.sh' 35 | 36 | - name: Run coordinator tests 37 | run: | 38 | docker compose run --no-TTY --rm --entrypoint bash dev -c './scripts/test_coverage.sh' 39 | 40 | - name: Upload Coverage 41 | if: success() 42 | uses: coverallsapp/github-action@v1.2.4 43 | with: 44 | github-token: ${{ secrets.GITHUB_TOKEN }} 45 | path-to-lcov: build/coverage-report.lcov 46 | git-branch: ${{ (github.event.pull_request && format('refs/pull/{0}/merge', github.event.pull_request.number)) || github.ref }} 47 | git-commit: ${{ (github.event.pull_request && github.event.pull_request.merge.sha) || github.sha }} 48 | -------------------------------------------------------------------------------- /.github/workflows/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | tag=$(git tag --points-at HEAD) 6 | 7 | if [ -z "$tag" ]; then 8 | tag='latest' 9 | fi 10 | 11 | docker buildx create --name mybuilder --use || echo 'skip' 12 | docker buildx inspect --bootstrap 13 | 14 | dockerfile="docker/${TARGET}/Dockerfile" 15 | path=$(dirname "${dockerfile}") 16 | ext=${path##*/} 17 | image="ghcr.io/$GITHUB_REPOSITORY/$ext" 18 | 19 | docker buildx build \ 20 | --progress plain \ 21 | --cache-from "type=registry,ref=${image}-ci-cache:latest" \ 22 | --cache-to "type=registry,ref=${image}-ci-cache:latest,mode=max" \ 23 | --compress \ 24 | --push \ 25 | --platform "$PLATFORM" \ 26 | -t "$image:$tag" \ 27 | -f "${dockerfile}" . 28 | docker buildx imagetools inspect "$image:$tag" 29 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: 3 | workflow_dispatch: 4 | workflow_run: 5 | workflows: [coverage] 6 | types: [completed] 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' 13 | runs-on: ["${{github.run_id}}", self-hosted, r6a.xlarge, "${{matrix.TARGET}}"] 14 | strategy: 15 | matrix: 16 | TARGET: ['coordinator', 'web', 'prover', 'geth', 'dev'] 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Login to ghcr.io 21 | env: 22 | PAT: ${{ secrets.GITHUB_TOKEN }} 23 | run: printf "$PAT" | docker login --username _ --password-stdin ghcr.io 24 | 25 | - name: Build and push Docker images 26 | env: 27 | TARGET: ${{ matrix.TARGET }} 28 | PLATFORM: 'linux/amd64,linux/arm64' 29 | run: ./.github/workflows/docker.sh 30 | -------------------------------------------------------------------------------- /.github/workflows/github-ops.yml: -------------------------------------------------------------------------------- 1 | name: github-ops 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_run: 6 | workflows: ["*"] 7 | types: 8 | - requested 9 | - completed 10 | 11 | concurrency: 12 | group: ${{ github.workflow }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | ping: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 1 19 | steps: 20 | - name: Ping runner workflow 21 | env: 22 | # only needs github actions (write) `workflow_dispatch` 23 | GH_OPS_PAT: ${{ secrets.PAT_OPS_TRIGGER }} 24 | # change `zkevm-chain.yml` to the desired workflow 25 | run: | 26 | curl -H "authorization: token $GH_OPS_PAT" 'https://api.github.com/repos/privacy-scaling-explorations/github-ops/actions/workflows/zkevm-chain.yml/dispatches' -d '{"ref": "master"}' 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | # We only run these lints on trial-merges of PRs to reduce noise. 4 | on: 5 | merge_group: 6 | pull_request: 7 | types: [synchronize, opened, reopened, ready_for_review] 8 | push: 9 | branches: 10 | - main 11 | - master 12 | 13 | jobs: 14 | lint: 15 | if: github.event.pull_request.draft == false 16 | 17 | timeout-minutes: 30 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | components: rustfmt, clippy 25 | override: false 26 | 27 | # Go cache for building geth-utils 28 | - name: Go cache 29 | uses: actions/cache@v3 30 | with: 31 | path: | 32 | ~/.cache/go-build 33 | ~/go/pkg/mod 34 | key: ${{ runner.os }}-go-${{ hashFiles('**/Cargo.lock') }} 35 | restore-keys: | 36 | ${{ runner.os }}-go- 37 | 38 | - name: Cargo cache 39 | uses: actions/cache@v3 40 | with: 41 | path: | 42 | ~/.cargo/bin/ 43 | ~/.cargo/registry/index/ 44 | ~/.cargo/registry/cache/ 45 | ~/.cargo/git/db/ 46 | target/ 47 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 48 | 49 | - name: Run cargo clippy 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: clippy 53 | args: --all-features --all-targets -- -D warnings 54 | 55 | - name: Run cargo fmt 56 | uses: actions-rs/cargo@v1 57 | with: 58 | command: fmt 59 | args: --all -- --check 60 | -------------------------------------------------------------------------------- /.github/workflows/pi-eth-transfer.yml: -------------------------------------------------------------------------------- 1 | name: Pi Circuit aggregation eth_transfer 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 0 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-pi-eth-transfer') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: pi 15 | test-name: eth_transfer 16 | instance-type: r6a.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/pi-native-withdraw.yml: -------------------------------------------------------------------------------- 1 | name: Pi Circuit aggregation native_withdraw 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 0 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-pi-native-withdraw') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: pi 15 | test-name: native_withdraw 16 | instance-type: r6a.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/super-eth-transfer.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation eth_transfer 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 0 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-super-eth-transfer') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: super 15 | test-name: eth_transfer 16 | instance-type: r6a.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/super-native-withdraw.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation native_withdraw 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 0 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-super-native-withdraw') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: super 15 | test-name: native_withdraw 16 | instance-type: x2iedn.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/super-worst-case-keccak-0-32.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation worst_case_keccak_0_32 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 5 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-super-worst-case-keccak-0-32') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: super 15 | test-name: worst_case_keccak_0_32 16 | instance-type: x2iedn.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/super-worst-case-mload.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation worst_case_mload 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 15 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-super-worst-case-mload') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: super 15 | test-name: worst_case_mload 16 | instance-type: x2iedn.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/super-worst-case-smod.yml: -------------------------------------------------------------------------------- 1 | name: Super Circuit aggregation worst_case_smod 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: [synchronize, opened, reopened, labeled] 6 | schedule: 7 | - cron: '0 23 * * 6' 8 | 9 | jobs: 10 | test: 11 | if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci-super-worst-case-smod') 12 | uses: ./.github/workflows/circuit-test.yml 13 | with: 14 | circuit: super 15 | test-name: worst_case_smod 16 | instance-type: x2iedn.24xlarge 17 | concurrency-group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number }} 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.env 2 | build/ 3 | target/ 4 | .idea/ 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "coordinator", 4 | "prover", 5 | "dev" 6 | ] 7 | 8 | [profile.release] 9 | opt-level = 3 10 | lto = "thin" 11 | 12 | [patch."https://github.com/privacy-scaling-explorations/halo2.git"] 13 | halo2_proofs = { git = "https://github.com/appliedzkp/halo2.git", tag = "v2023_04_20", package = "halo2_proofs" } 14 | 15 | # [patch."https://github.com/privacy-scaling-explorations/halo2curves.git"] 16 | # halo2_curves = { git = "https://github.com/appliedzkp/halo2curves.git", tag = "0.3.3", package = "halo2curves" } 17 | 18 | [patch."https://github.com/privacy-scaling-explorations/halo2wrong.git"] 19 | halo2_wrong_ecc = { git = "https://github.com/appliedzkp/halo2wrong.git", tag = "v2023_04_20", package = "ecc" } 20 | halo2_wrong_integer = { git = "https://github.com/appliedzkp/halo2wrong.git", tag = "v2023_04_20", package = "integer" } 21 | halo2_wrong_maingate = { git = "https://github.com/appliedzkp/halo2wrong.git", tag = "v2023_04_20", package = "maingate" } 22 | 23 | #[patch."https://github.com/privacy-scaling-explorations/zkevm-circuits.git"] 24 | #bus-mapping = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "zkevm-chain" } 25 | #eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "zkevm-chain" } 26 | #zkevm-circuits = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "zkevm-chain" } 27 | #mock = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "zkevm-chain" } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated - zkevm-chain 2 | 3 | **Important Notice:** This repository is no longer actively maintained or supported, and contains a vulnerability in the smart contracts. 4 | 5 | For some months already we have determined that the scope of our project doesn't involve building an L2 zkRollup. Our main goals are: build a system to generate zk proofs of correct Ethereum Block processing, and from this create a project where proofs for each L1 block are generated so that light clients can sync with very low resources requirement. A secondary goal is to offer the Block zk proof system as a library/toolkit for others to build a zkRollup on top of it. This means we're not actively developing mechanisms related to managing an L2 (decision for making blocks, coordinators, bridging, etc.). The zkevm-chain repo started with that aim but after our shift in focus it remained as an infrastructure for end-to-end testing of blocks zk proofs and benchmarking. 6 | 7 | This repository contains an [**unfixed DoS vulnerability** in the smart contracts code](https://github.com/privacy-scaling-explorations/zkevm-chain/security/advisories/GHSA-6m99-mxg3-pv9h); please be aware of this if you decide use this repository in any way. 8 | -------------------------------------------------------------------------------- /README.original.md: -------------------------------------------------------------------------------- 1 | [![Coverage Status](https://coveralls.io/repos/github/privacy-scaling-explorations/zkevm-chain/badge.svg?branch=master)](https://coveralls.io/github/privacy-scaling-explorations/zkevm-chain?branch=master) 2 | 3 | ###### Prover Integration Test Status 4 | [![Pi Circuit aggregation eth_transfer](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/pi-eth-transfer.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/pi-eth-transfer.yml) 5 | [![Pi Circuit aggregation native_withdraw](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/pi-native-withdraw.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/pi-native-withdraw.yml) 6 | [![Super Circuit aggregation eth_transfer](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-eth-transfer.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-eth-transfer.yml) 7 | [![Super Circuit aggregation native_withdraw](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-native-withdraw.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-native-withdraw.yml) 8 | [![Super Circuit aggregation worst_case_mload](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-worst-case-mload.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-worst-case-mload.yml) 9 | [![Super Circuit aggregation worst_case_smod](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-worst-case-smod.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-worst-case-smod.yml) 10 | [![Super Circuit aggregation worst_case_keccak_0_32](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-worst-case-keccak-0-32.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-chain/actions/workflows/super-worst-case-keccak-0-32.yml) 11 | 12 | ## Structure 13 | 14 | |Path|Description| 15 | |-|-| 16 | |`coordinator/`|coordinator daemon| 17 | |`contracts/`|l1/l2 bridge contracts| 18 | |`docker/`|dockerfiles for various purposes| 19 | |`scripts/`|helpful scripts| 20 | 21 | ## Setup 22 | `cp .env.example .env` and edit the values. The account you specify in that file will be the miner of the clique network and will have ETH allocated in the genesis block. 23 | 24 | If you make changes to the genesis file, then you have to delete the existing chain via `docker compose down --volumes` - this will delete any volumes associated with this setup. 25 | Use `DOCKER_BUILDKIT=1 docker compose up` to start the chain. 26 | 27 | You can use `./scripts/dev.sh` to start a local dev environment without the coordinator-service that drops you into a shell. Useful if you want to work on the `coordinator/` daemon. 28 | 29 | ## Developer workflows 30 | ###### Testing the coordinator & prover with `cargo test` 31 | Enter the developer service via `./scripts/dev.sh`. 32 | Inside that shell you can use this wrapper script to build & start the `prover_rpcd` in the background and invoke `cargo test -- eth_transfer`: 33 | ``` 34 | ./scripts/test_prover.sh eth_transfer 35 | ``` 36 | The output of the prover damon will be piped to `PROVER_LOG.txt`. 37 | If you need fixtures for the L2 block with number 1, then use `./scripts/get_block_fixtures.sh $COORDINATOR_L2_RPC_URL 1` to retrieve it for you. 38 | 39 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkevm_common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "main", features = ["warn-unimplemented"] } 9 | hyper = { version = "0.14.16", features = ["server"] } 10 | log = "0.4.14" 11 | serde = { version = "1.0.136", features = ["derive"] } 12 | serde_json = "1.0.78" 13 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread"] } 14 | -------------------------------------------------------------------------------- /common/src/json_rpc.rs: -------------------------------------------------------------------------------- 1 | /// Common utilities for json-rpc 2 | use hyper::body::Buf; 3 | use hyper::client::HttpConnector; 4 | use hyper::Body; 5 | use hyper::Request; 6 | use hyper::Uri; 7 | 8 | use serde::de::DeserializeOwned; 9 | use serde::Deserialize; 10 | use serde::Serialize; 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | pub struct JsonRpcError { 14 | pub code: i32, 15 | pub message: String, 16 | } 17 | 18 | #[derive(Debug, Serialize)] 19 | pub struct JsonRpcResponseError { 20 | pub jsonrpc: String, 21 | pub id: serde_json::Value, 22 | pub error: JsonRpcError, 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize)] 26 | pub struct JsonRpcResponse { 27 | pub jsonrpc: String, 28 | pub id: serde_json::Value, 29 | pub result: Option, 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize)] 33 | pub struct JsonRpcRequest { 34 | pub jsonrpc: String, 35 | pub id: serde_json::Value, 36 | pub method: String, 37 | pub params: T, 38 | } 39 | 40 | /// Invokes a `json-rpc` request with a timeout of `timeout` ms for the network 41 | /// and deserialize part. 42 | pub async fn jsonrpc_request_client( 43 | timeout: u64, 44 | client: &hyper::Client, 45 | uri: &Uri, 46 | method: &str, 47 | params: T, 48 | ) -> Result { 49 | #[derive(Debug, Deserialize)] 50 | struct JsonRpcResponseInternal { 51 | result: Option, 52 | error: Option, 53 | } 54 | 55 | let node_req = Request::post(uri); 56 | let req_obj = JsonRpcRequest { 57 | jsonrpc: "2.0".to_string(), 58 | id: 0.into(), 59 | method: method.to_string(), 60 | params, 61 | }; 62 | 63 | let node_req = node_req 64 | .header(hyper::header::CONTENT_TYPE, "application/json") 65 | .body(Body::from(serde_json::to_vec(&req_obj).unwrap())) 66 | .unwrap(); 67 | 68 | log::trace!("jsonrpc_request_client: {} {}", uri, method); 69 | 70 | let json = tokio::time::timeout(std::time::Duration::from_millis(timeout), async { 71 | let err_str = &uri.to_string(); 72 | let resp = client.request(node_req).await.expect(err_str); 73 | let body = hyper::body::aggregate(resp).await.expect(err_str); 74 | let json: JsonRpcResponseInternal = 75 | serde_json::from_reader(body.reader()).expect(err_str); 76 | 77 | json 78 | }) 79 | .await 80 | .map_err(|err| format!("jsonrpc: uri={uri} method={method} error={err}"))?; 81 | 82 | if json.error.is_some() { 83 | return Err(json.error.unwrap().message); 84 | } 85 | 86 | if json.result.is_none() { 87 | return Err("no result in response".to_string()); 88 | } 89 | 90 | Ok(json.result.unwrap()) 91 | } 92 | 93 | pub async fn jsonrpc_request( 94 | uri: &Uri, 95 | method: &str, 96 | params: T, 97 | ) -> Result { 98 | let client = hyper::Client::new(); 99 | jsonrpc_request_client(30_000, &client, uri, method, params).await 100 | } 101 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod json_rpc; 2 | pub mod prover; 3 | -------------------------------------------------------------------------------- /common/src/prover.rs: -------------------------------------------------------------------------------- 1 | use eth_types::{Bytes, U256}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Default, Serialize, Deserialize)] 5 | pub struct ProofResult { 6 | /// The halo2 transcript 7 | pub proof: Bytes, 8 | /// Public inputs for the proof 9 | pub instance: Vec, 10 | /// k of circuit parameters 11 | pub k: u8, 12 | /// Randomness used 13 | pub randomness: Bytes, 14 | /// Circuit name / identifier 15 | pub label: String, 16 | /// Auxiliary 17 | pub aux: ProofResultInstrumentation, 18 | } 19 | 20 | impl std::fmt::Debug for ProofResult { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | f.debug_struct("Proof") 23 | .field("proof", &format!("{}", &self.proof)) 24 | .field("instance", &self.instance) 25 | .field("k", &self.k) 26 | .field("randomness", &format!("{}", &self.randomness)) 27 | .field("aux", &format!("{:#?}", self.aux)) 28 | .finish() 29 | } 30 | } 31 | 32 | /// Timing information in milliseconds. 33 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 34 | pub struct ProofResultInstrumentation { 35 | /// keygen_vk 36 | pub vk: u32, 37 | /// keygen_pk 38 | pub pk: u32, 39 | /// create_proof 40 | pub proof: u32, 41 | /// verify_proof 42 | pub verify: u32, 43 | /// MockProver.verify_par 44 | pub mock: u32, 45 | /// Circuit::new 46 | pub circuit: u32, 47 | /// RootCircuit::compile 48 | pub protocol: u32, 49 | } 50 | 51 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 52 | pub struct Proofs { 53 | /// Circuit configuration used 54 | pub config: CircuitConfig, 55 | // Proof result for circuit 56 | pub circuit: ProofResult, 57 | /// Aggregation proof for circuit, if requested 58 | pub aggregation: ProofResult, 59 | /// Gas used. Determines the upper ceiling for circuit parameters 60 | pub gas: u64, 61 | } 62 | 63 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 64 | pub struct ProofRequestOptions { 65 | /// The name of the circuit. 66 | /// "super", "pi" 67 | pub circuit: String, 68 | /// the block number 69 | pub block: u64, 70 | /// the rpc url 71 | pub rpc: String, 72 | /// retry proof computation if error 73 | pub retry: bool, 74 | /// Parameters file or directory to use. 75 | /// Otherwise generates them on the fly. 76 | pub param: Option, 77 | /// Only use MockProver if true. 78 | #[serde(default = "default_bool")] 79 | pub mock: bool, 80 | /// Additionaly aggregates the circuit proof if true 81 | #[serde(default = "default_bool")] 82 | pub aggregate: bool, 83 | /// Runs the MockProver if proofing fails. 84 | #[serde(default = "default_bool")] 85 | pub mock_feedback: bool, 86 | /// Verifies the proof after computation. 87 | #[serde(default = "default_bool")] 88 | pub verify_proof: bool, 89 | } 90 | 91 | impl PartialEq for ProofRequestOptions { 92 | fn eq(&self, other: &Self) -> bool { 93 | self.block == other.block 94 | && self.rpc == other.rpc 95 | && self.param == other.param 96 | && self.circuit == other.circuit 97 | && self.mock == other.mock 98 | && self.aggregate == other.aggregate 99 | } 100 | } 101 | 102 | #[derive(Debug, Clone, Serialize, Deserialize)] 103 | pub struct ProofRequest { 104 | pub options: ProofRequestOptions, 105 | pub result: Option>, 106 | /// A counter to keep track of changes of the `result` field 107 | pub edition: u64, 108 | } 109 | 110 | #[derive(Debug, Serialize, Deserialize)] 111 | pub struct NodeInformation { 112 | pub id: String, 113 | pub tasks: Vec, 114 | } 115 | 116 | #[derive(Debug, Serialize, Deserialize)] 117 | pub struct NodeStatus { 118 | pub id: String, 119 | /// The current active task this instance wants to obtain or is working on. 120 | pub task: Option, 121 | /// `true` if this instance started working on `task` 122 | pub obtained: bool, 123 | } 124 | 125 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 126 | pub struct CircuitConfig { 127 | pub block_gas_limit: usize, 128 | pub max_txs: usize, 129 | pub max_calldata: usize, 130 | pub max_bytecode: usize, 131 | pub max_rws: usize, 132 | pub max_copy_rows: usize, 133 | pub max_exp_steps: usize, 134 | pub min_k: usize, 135 | pub pad_to: usize, 136 | pub min_k_aggregation: usize, 137 | pub keccak_padding: usize, 138 | } 139 | 140 | fn default_bool() -> bool { 141 | false 142 | } 143 | -------------------------------------------------------------------------------- /contracts/Multicall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract Multicall { 5 | /// @notice Used to repeatly call to self. 6 | /// Expects [length of bytes (4 bytes), bytes, ...] appended directly after the function signature. 7 | /// Reverts as soon a call fails. 8 | function multicall () external { 9 | assembly { 10 | // starts after function signature (4 bytes) 11 | for { let ptr := 4 } lt(ptr, calldatasize()) {} { 12 | let len := shr(224, calldataload(ptr)) 13 | ptr := add(ptr, 4) 14 | calldatacopy(0, ptr, len) 15 | ptr := add(ptr, len) 16 | let success := call(gas(), address(), 0, 0, len, 0, 0) 17 | switch success 18 | case 0 { 19 | returndatacopy(0, 0, returndatasize()) 20 | revert(0, returndatasize()) 21 | } 22 | } 23 | 24 | stop() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | /// @notice Callforwarding proxy 5 | contract Proxy { 6 | constructor (address initialImplementation) { 7 | assembly { 8 | // stores the initial contract address to forward calls 9 | sstore(not(returndatasize()), initialImplementation) 10 | } 11 | } 12 | 13 | fallback () external payable { 14 | assembly { 15 | // copy all calldata into memory - returndatasize() is a substitute for `0` 16 | calldatacopy(returndatasize(), returndatasize(), calldatasize()) 17 | // keep a copy to be used after the call 18 | let zero := returndatasize() 19 | // call contract address loaded from storage slot with key `uint256(-1)` 20 | let success := delegatecall( 21 | gas(), 22 | sload(not(returndatasize())), 23 | returndatasize(), 24 | calldatasize(), 25 | returndatasize(), 26 | returndatasize() 27 | ) 28 | 29 | // copy all return data into memory 30 | returndatacopy(zero, zero, returndatasize()) 31 | 32 | // if the delegatecall succeeded, then return 33 | if success { 34 | return(zero, returndatasize()) 35 | } 36 | // else revert 37 | revert(zero, returndatasize()) 38 | } 39 | } 40 | 41 | /// @notice For testing purposes only. 42 | function upgrade (address to) external { 43 | assembly { 44 | // stores the contract address to forward calls 45 | sstore(not(returndatasize()), to) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/ZkEvmBridgeEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract ZkEvmBridgeEvents { 5 | event BlockSubmitted(); 6 | event BlockFinalized(bytes32 blockHash); 7 | 8 | event MessageDispatched(address from, address to, uint256 value, uint256 fee, uint256 deadline, uint256 nonce, bytes data); 9 | event MessageDelivered(bytes32 id); 10 | event MessageDropped(bytes32 id); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/ZkEvmL1Bridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './ZkEvmUtils.sol'; 5 | import './ZkEvmMagicNumbers.sol'; 6 | import './ZkEvmBridgeEvents.sol'; 7 | import './ZkEvmMessageDispatcherBase.sol'; 8 | import './ZkEvmMessageDelivererBase.sol'; 9 | import './interfaces/IZkEvmMessageDispatcher.sol'; 10 | import './interfaces/IZkEvmMessageDelivererWithProof.sol'; 11 | import './generated/PatriciaAccountValidator.sol'; 12 | import './generated/PatriciaStorageValidator.sol'; 13 | import './generated/PublicInput.sol'; 14 | import './generated/HeaderUtil.sol'; 15 | import './generated/CircuitConfig.sol'; 16 | import './Multicall.sol'; 17 | 18 | contract ZkEvmL1Bridge is 19 | ZkEvmUtils, 20 | ZkEvmMagicNumbers, 21 | ZkEvmBridgeEvents, 22 | ZkEvmMessageDispatcherBase, 23 | ZkEvmMessageDelivererBase, 24 | IZkEvmMessageDispatcher, 25 | IZkEvmMessageDelivererWithProof, 26 | PatriciaAccountValidator, 27 | PatriciaStorageValidator, 28 | PublicInput, 29 | HeaderUtil, 30 | CircuitConfig, 31 | Multicall 32 | { 33 | // TODO: Move storage to static slots 34 | mapping (bytes32 => bytes32) commitments; 35 | mapping (bytes32 => bytes32) public stateRoots; 36 | mapping (bytes32 => uint256) originTimestamps; 37 | 38 | function buildCommitment(bytes calldata witness) public view returns (uint256[] memory result) { 39 | ( 40 | bytes32 parentBlockHash, 41 | , 42 | , 43 | , 44 | uint256 blockGas, 45 | ) = _readHeaderParts(witness); 46 | uint256 parentStateRoot = uint256(stateRoots[parentBlockHash]); 47 | uint256 chainId = 99; 48 | (uint256 MAX_TXS, uint256 MAX_CALLDATA) = _getCircuitConfig(blockGas); 49 | 50 | result = _buildCommitment(MAX_TXS, MAX_CALLDATA, chainId, parentStateRoot, witness, true); 51 | } 52 | 53 | function submitBlock (bytes calldata witness) external { 54 | _onlyEOA(); 55 | emit BlockSubmitted(); 56 | 57 | ( 58 | bytes32 parentBlockHash, 59 | bytes32 blockHash, 60 | bytes32 blockStateRoot, 61 | , 62 | uint256 blockGas, 63 | ) = _readHeaderParts(witness); 64 | uint256 parentStateRoot = uint256(stateRoots[parentBlockHash]); 65 | uint256 chainId = 99; 66 | (uint256 MAX_TXS, uint256 MAX_CALLDATA) = _getCircuitConfig(blockGas); 67 | 68 | uint256[] memory publicInput = 69 | _buildCommitment(MAX_TXS, MAX_CALLDATA, chainId, parentStateRoot, witness, true); 70 | 71 | bytes32 hash; 72 | assembly { 73 | hash := keccak256(add(publicInput, 32), mul(mload(publicInput), 32)) 74 | } 75 | commitments[blockHash] = hash; 76 | stateRoots[blockHash] = blockStateRoot; 77 | } 78 | 79 | /// @dev 80 | /// proof layout (bytes) 81 | /// - block hash 82 | /// - verifier address (TODO: should be checked against allowed verifier addresses) 83 | /// - proof instance - first 5 elements commitment 84 | /// - proof transcript 85 | function finalizeBlock (bytes calldata proof) external { 86 | require(proof.length > 511, "PROOF_LEN"); 87 | 88 | bytes32 blockHash; 89 | assembly { 90 | blockHash := calldataload(proof.offset) 91 | } 92 | bytes32 expectedCommitmentHash = commitments[blockHash]; 93 | 94 | assembly { 95 | // function Error(string) 96 | function revertWith (msg) { 97 | mstore(0, shl(224, 0x08c379a0)) 98 | mstore(4, 32) 99 | mstore(68, msg) 100 | let msgLen 101 | for {} msg {} { 102 | msg := shl(8, msg) 103 | msgLen := add(msgLen, 1) 104 | } 105 | mstore(36, msgLen) 106 | revert(0, 100) 107 | } 108 | 109 | // verify commitment hash 110 | { 111 | // 5 * 32 112 | let len := 160 113 | let ptr := mload(64) 114 | // skip `blockHash, address, is_aggregated` 115 | calldatacopy(ptr, add(proof.offset, 96), len) 116 | let hash := keccak256(ptr, len) 117 | if iszero(eq(hash, expectedCommitmentHash)) { 118 | revertWith("commitment hash") 119 | } 120 | } 121 | 122 | { 123 | // call contract at `addr` for proof verification 124 | let offset := add(proof.offset, 32) 125 | let addr := calldataload(offset) 126 | switch extcodesize(addr) 127 | case 0 { 128 | // no code at `addr` 129 | revertWith("verifier missing") 130 | } 131 | 132 | let len := sub(proof.length, 96) 133 | offset := add(offset, 64) 134 | let memPtr := mload(64) 135 | calldatacopy(memPtr, offset, len) 136 | let success := staticcall(gas(), addr, memPtr, len, 0, 0) 137 | switch success 138 | case 0 { 139 | // plonk verification failed 140 | //returndatacopy(0, 0, returndatasize()) 141 | //revert(0, returndatasize()) 142 | revertWith("verifier failed") 143 | } 144 | } 145 | } 146 | 147 | emit BlockFinalized(blockHash); 148 | } 149 | 150 | /// @inheritdoc IZkEvmMessageDelivererWithProof 151 | function deliverMessageWithProof ( 152 | address from, 153 | address to, 154 | uint256 value, 155 | uint256 fee, 156 | uint256 deadline, 157 | uint256 nonce, 158 | bytes calldata data, 159 | bytes calldata proof 160 | ) external { 161 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 162 | (bytes32 proofStorageRoot, bytes32 storageValue) = _validatePatriciaStorageProof( 163 | _PENDING_MESSAGE_KEY(messageHash), 164 | proof 165 | ); 166 | require(originTimestamps[proofStorageRoot] != 0, 'DMROOT'); 167 | require(storageValue == bytes32(uint256(1)), 'DMVAL'); 168 | 169 | _deliverMessage(from, to, value, fee, deadline, nonce, data); 170 | } 171 | 172 | /// @inheritdoc IZkEvmMessageDelivererWithProof 173 | function getTimestampForStorageRoot (bytes32 val) public view returns (uint256) { 174 | return originTimestamps[val]; 175 | } 176 | 177 | /// @inheritdoc IZkEvmMessageDelivererWithProof 178 | function importForeignBridgeState (bytes calldata blockHeader, bytes calldata accountProof) external { 179 | ( 180 | , 181 | bytes32 blockHash, 182 | , 183 | , 184 | , 185 | uint256 timestamp 186 | ) = _readHeaderParts(blockHeader); 187 | 188 | (bytes32 proofStateRoot, bytes32 proofStorageRoot) = _validatePatriciaAccountProof( 189 | L2_DISPATCHER, 190 | accountProof 191 | ); 192 | bytes32 stateRoot = stateRoots[blockHash]; 193 | require(stateRoot != 0, 'BLOCK'); 194 | require(proofStateRoot == stateRoot, 'IBROOT'); 195 | require(proofStorageRoot != 0, 'IBSTROOT'); 196 | require(timestamp != 0, 'IBTS'); 197 | originTimestamps[proofStorageRoot] = timestamp; 198 | 199 | emit ForeignBridgeStateImported(blockHash, stateRoot, timestamp); 200 | } 201 | 202 | /// @inheritdoc IZkEvmMessageDispatcher 203 | function dispatchMessage ( 204 | address to, 205 | uint256 fee, 206 | uint256 deadline, 207 | uint256 nonce, 208 | bytes calldata data 209 | ) external payable returns (bytes32 messageHash) { 210 | messageHash = _dispatchMessage(to, fee, deadline, nonce, data); 211 | } 212 | 213 | /// @inheritdoc IZkEvmMessageDispatcher 214 | function dropMessage ( 215 | address from, 216 | address to, 217 | uint256 value, 218 | uint256 fee, 219 | uint256 deadline, 220 | uint256 nonce, 221 | bytes calldata data, 222 | bytes calldata proof 223 | ) external { 224 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 225 | (bytes32 proofStorageRoot, bytes32 storageValue) = _validatePatriciaStorageProof( 226 | _PENDING_MESSAGE_KEY(messageHash), 227 | proof 228 | ); 229 | require(storageValue == 0, 'DMVAL'); 230 | uint256 originTimestamp = originTimestamps[proofStorageRoot]; 231 | require(originTimestamp > deadline, 'DMTS'); 232 | 233 | _dropMessage(from, to, value, fee, deadline, nonce, data); 234 | } 235 | 236 | /// @dev For testing purposes 237 | function initGenesis (bytes32 _blockHash, bytes32 _stateRoot) external { 238 | stateRoots[_blockHash] = _stateRoot; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /contracts/ZkEvmL2MessageDeliverer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './ZkEvmMessageDelivererBase.sol'; 5 | import './interfaces/IZkEvmMessageDelivererWithProof.sol'; 6 | import './generated/PatriciaAccountValidator.sol'; 7 | import './generated/PatriciaStorageValidator.sol'; 8 | import './ZkEvmStorage.sol'; 9 | import './generated/CommonBlockOperations.sol'; 10 | 11 | contract ZkEvmL2MessageDeliverer is 12 | ZkEvmMessageDelivererBase, 13 | IZkEvmMessageDelivererWithProof, 14 | ZkEvmStorage, 15 | PatriciaAccountValidator, 16 | PatriciaStorageValidator, 17 | CommonBlockOperations 18 | { 19 | // TODO: decide on public getters once L1/L2 Inbox is DRY 20 | // Latest known L1 block hash 21 | bytes32 lastKnownL1BlockHash; 22 | // Mapping from to L1 block timestamp 23 | mapping (bytes32 => uint256) storageRootToTimestamp; 24 | 25 | /// @inheritdoc IZkEvmMessageDelivererWithProof 26 | function getTimestampForStorageRoot (bytes32 val) public view returns (uint256) { 27 | return storageRootToTimestamp[val]; 28 | } 29 | 30 | /// @dev `blockNumber` & `blockHash` must be checked by the L1 verification step(s). 31 | function importForeignBlock ( 32 | uint256 /*blockNumber*/, 33 | bytes32 blockHash 34 | ) external { 35 | _onlyEOA(); 36 | // should be restricted to block producer set 37 | // require(msg.sender == block.coinbase, 'IBH'); 38 | lastKnownL1BlockHash = blockHash; 39 | } 40 | 41 | /// @inheritdoc IZkEvmMessageDelivererWithProof 42 | function importForeignBridgeState (bytes calldata blockHeader, bytes calldata accountProof) external { 43 | (bytes32 hash, bytes32 stateRoot, uint256 timestamp) = _readBlockHeader(blockHeader); 44 | require(hash == lastKnownL1BlockHash, 'HASH'); 45 | 46 | (bytes32 proofStateRoot, bytes32 proofStorageRoot) = _validatePatriciaAccountProof( 47 | L1_BRIDGE, 48 | accountProof 49 | ); 50 | require(proofStateRoot == stateRoot, 'DMROOT'); 51 | storageRootToTimestamp[proofStorageRoot] = timestamp; 52 | 53 | emit ForeignBridgeStateImported(hash, stateRoot, timestamp); 54 | } 55 | 56 | /// @inheritdoc IZkEvmMessageDelivererWithProof 57 | function deliverMessageWithProof ( 58 | address from, 59 | address to, 60 | uint256 value, 61 | uint256 fee, 62 | uint256 deadline, 63 | uint256 nonce, 64 | bytes calldata data, 65 | bytes calldata storageProof 66 | ) external { 67 | // avoid calling the 'requestETH' or any other 'administrative' functions from L2_DELIVERER 68 | require(to != L2_DISPATCHER, 'TNED'); 69 | 70 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 71 | (bytes32 storageRoot, bytes32 storageValue) = _validatePatriciaStorageProof( 72 | _PENDING_MESSAGE_KEY(messageHash), 73 | storageProof 74 | ); 75 | uint256 originTimestamp = storageRootToTimestamp[storageRoot]; 76 | require(originTimestamp != 0, 'STROOT'); 77 | require(originTimestamp < deadline, 'DMTS'); 78 | require(storageValue == bytes32(uint256(1)), 'DMVAL'); 79 | 80 | _deliverMessage(from, to, value, fee, deadline, nonce, data); 81 | } 82 | 83 | function requestETH (uint256 amount) external { 84 | require(msg.sender == L2_DISPATCHER, 'MSEDS'); 85 | 86 | _transferETH(msg.sender, amount); 87 | } 88 | 89 | receive () external payable { 90 | require(msg.sender == L2_DISPATCHER, 'MSEDS'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /contracts/ZkEvmL2MessageDispatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './interfaces/IZkEvmMessageDispatcher.sol'; 5 | import './interfaces/IZkEvmMessageDelivererWithProof.sol'; 6 | import './ZkEvmMessageDispatcherBase.sol'; 7 | import './generated/PatriciaStorageValidator.sol'; 8 | 9 | contract ZkEvmL2MessageDispatcher is 10 | IZkEvmMessageDispatcher, 11 | ZkEvmMessageDispatcherBase, 12 | PatriciaStorageValidator 13 | { 14 | /// @inheritdoc IZkEvmMessageDispatcher 15 | function dispatchMessage ( 16 | address to, 17 | uint256 fee, 18 | uint256 deadline, 19 | uint256 nonce, 20 | bytes calldata data 21 | ) override external payable returns (bytes32 messageHash) { 22 | // send ETH to L2_DELIVERER 23 | if (msg.value != 0) { 24 | _transferETH(L2_DELIVERER, msg.value); 25 | } 26 | messageHash = _dispatchMessage(to, fee, deadline, nonce, data); 27 | } 28 | 29 | /// @inheritdoc IZkEvmMessageDispatcher 30 | function dropMessage ( 31 | address from, 32 | address to, 33 | uint256 value, 34 | uint256 fee, 35 | uint256 deadline, 36 | uint256 nonce, 37 | bytes calldata data, 38 | bytes calldata proof 39 | ) override external { 40 | // acquire ETH from L2_DELIVERER 41 | uint256 amount = value + fee; 42 | if (amount != 0) { 43 | (bool success,) = L2_DELIVERER.call(abi.encodeWithSignature('requestETH(uint256)', amount)); 44 | require(success, 'RQETH'); 45 | } 46 | // validate proof 47 | { 48 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 49 | (bytes32 rootHash, bytes32 storageValue) = _validatePatriciaStorageProof( 50 | _PENDING_MESSAGE_KEY(messageHash), 51 | proof 52 | ); 53 | require(storageValue == 0, 'DMVAL'); 54 | // verify rootHash 55 | require(rootHash != 0, 'STROOT'); 56 | uint256 originTimestamp = IZkEvmMessageDelivererWithProof(L2_DELIVERER).getTimestampForStorageRoot(rootHash); 57 | require(originTimestamp > deadline, 'DMTS'); 58 | } 59 | 60 | _dropMessage(from, to, value, fee, deadline, nonce, data); 61 | } 62 | 63 | receive () external payable { 64 | require(msg.sender == L2_DELIVERER, 'MSED'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/ZkEvmMagicNumbers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract ZkEvmMagicNumbers { 5 | uint256 constant MIN_MESSAGE_LOCK_SECONDS = 7200; 6 | address constant L2_DELIVERER = 0x0000000000000000000000000000000000010000; 7 | address constant L2_DISPATCHER = 0x0000000000000000000000000000000000020000; 8 | address constant L1_BRIDGE = 0x936a70C0b28532AA22240dce21f89a8399d6ac60; 9 | 10 | address constant L1_OPTIMISM_WRAPPER = 0x936A70C0b28532aa22240dce21F89a8399d6aC61; 11 | address constant L2_OPTIMISM_WRAPPER = 0x4200000000000000000000000000000000000007; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/ZkEvmMessageDelivererBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './interfaces/IZkEvmMessageDelivererBase.sol'; 5 | import './ZkEvmUtils.sol'; 6 | import './ZkEvmMagicNumbers.sol'; 7 | import './ZkEvmBridgeEvents.sol'; 8 | 9 | contract ZkEvmMessageDelivererBase is 10 | IZkEvmMessageDelivererBase, 11 | ZkEvmUtils, 12 | ZkEvmMagicNumbers, 13 | ZkEvmBridgeEvents 14 | { 15 | address _messageOrigin; 16 | 17 | /// @inheritdoc IZkEvmMessageDelivererBase 18 | function messageOrigin () external view returns (address) { 19 | return _messageOrigin; 20 | } 21 | 22 | /// @dev Common routine for `deliverMessage` or `deliverMessageWithProof`. 23 | function _deliverMessage ( 24 | address from, 25 | address to, 26 | uint256 value, 27 | uint256 fee, 28 | uint256 deadline, 29 | uint256 nonce, 30 | bytes calldata data 31 | ) internal { 32 | require(block.timestamp < deadline, 'DMD'); 33 | 34 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 35 | 36 | if (fee != 0) { 37 | _transferETH(tx.origin, fee); 38 | } 39 | 40 | _messageOrigin = from; 41 | _callAccount(to, value, data); 42 | _messageOrigin = address(0); 43 | 44 | emit MessageDelivered(messageHash); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/ZkEvmMessageDispatcherBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './ZkEvmUtils.sol'; 5 | import './ZkEvmMagicNumbers.sol'; 6 | import './ZkEvmBridgeEvents.sol'; 7 | import './ZkEvmStorage.sol'; 8 | 9 | contract ZkEvmMessageDispatcherBase is ZkEvmUtils, ZkEvmMagicNumbers, ZkEvmBridgeEvents, ZkEvmStorage { 10 | function _dispatchMessage ( 11 | address to, 12 | uint256 fee, 13 | uint256 deadline, 14 | uint256 nonce, 15 | bytes calldata data 16 | ) internal returns (bytes32 messageHash) { 17 | // require(deadline > block.timestamp + MIN_MESSAGE_LOCK_SECONDS, 'DMD'); 18 | 19 | // assuming underflow check 20 | uint256 value = msg.value - fee; 21 | 22 | messageHash = keccak256(abi.encode(msg.sender, to, value, fee, deadline, nonce, data)); 23 | 24 | bytes32 storageSlot = _PENDING_MESSAGE_KEY(messageHash); 25 | require(_sload(storageSlot) == 0, 'DMH'); 26 | _sstore(storageSlot, bytes32(uint256(1))); 27 | 28 | emit MessageDispatched(msg.sender, to, value, fee, deadline, nonce, data); 29 | } 30 | 31 | function _dropMessage ( 32 | address from, 33 | address to, 34 | uint256 value, 35 | uint256 fee, 36 | uint256 deadline, 37 | uint256 nonce, 38 | bytes calldata data 39 | ) internal { 40 | require(block.timestamp > deadline, 'DMD'); 41 | 42 | bytes32 messageHash = keccak256(abi.encode(from, to, value, fee, deadline, nonce, data)); 43 | 44 | bytes32 storageSlot = _PENDING_MESSAGE_KEY(messageHash); 45 | require(_sload(storageSlot) != 0, 'DMH'); 46 | _sstore(storageSlot, 0); 47 | 48 | uint256 amount = value + fee; 49 | if (amount != 0) { 50 | _transferETH(from, amount); 51 | } 52 | 53 | emit MessageDropped(messageHash); 54 | } 55 | 56 | function _sload (bytes32 key) internal view returns (uint256 ret) { 57 | assembly { 58 | ret := sload(key) 59 | } 60 | } 61 | 62 | function _sstore (bytes32 key, bytes32 value) internal { 63 | assembly { 64 | sstore(key, value) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/ZkEvmStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | /// @notice Holds common functions for storage key calculations. 5 | contract ZkEvmStorage { 6 | function _PENDING_MESSAGE_KEY (bytes32 messageId) internal pure returns (bytes32 ret) { 7 | assembly { 8 | mstore(0, 0x31df76a4) 9 | mstore(32, messageId) 10 | ret := keccak256(0, 64) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/ZkEvmUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract ZkEvmUtils { 5 | /// @dev Revert if caller is not tx sender. 6 | function _onlyEOA () internal view { 7 | require(tx.origin == msg.sender, 'EOA'); 8 | } 9 | 10 | function _transferETH (address receiver, uint256 amount) internal { 11 | (bool success,) = receiver.call{ value: amount }(""); 12 | require(success, 'TETH'); 13 | } 14 | 15 | function _callAccount (address to, uint256 value, bytes calldata data) internal { 16 | assembly { 17 | let ptr := 128 18 | calldatacopy(ptr, data.offset, data.length) 19 | if iszero(call(gas(), to, value, ptr, data.length, 0, 0)) { 20 | returndatacopy(0, 0, returndatasize()) 21 | revert(0, returndatasize()) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/generated/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /contracts/generated/CircuitConfig.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity <0.9.0; 4 | contract CircuitConfig { 5 | function _getCircuitConfig (uint256 blockGasLimit) internal pure returns (uint256, uint256) { 6 | 7 | if (blockGasLimit <= 63000) { 8 | return (3, 10500); 9 | } 10 | 11 | if (blockGasLimit <= 300000) { 12 | return (14, 69750); 13 | } 14 | 15 | revert("CIRCUIT_CONFIG"); 16 | } 17 | } -------------------------------------------------------------------------------- /contracts/interfaces/ICrossDomainMessenger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >0.5.0 <0.9.0; 3 | 4 | /** 5 | * @title ICrossDomainMessenger 6 | */ 7 | interface ICrossDomainMessenger { 8 | /********** 9 | * Events * 10 | **********/ 11 | 12 | event SentMessage( 13 | address indexed target, 14 | address sender, 15 | bytes message, 16 | uint256 messageNonce, 17 | uint256 gasLimit 18 | ); 19 | event RelayedMessage(bytes32 indexed msgHash); 20 | event FailedRelayedMessage(bytes32 indexed msgHash); 21 | 22 | /************* 23 | * Variables * 24 | *************/ 25 | 26 | function xDomainMessageSender() external view returns (address); 27 | 28 | /******************** 29 | * Public Functions * 30 | ********************/ 31 | 32 | /** 33 | * Sends a cross domain message to the target messenger. 34 | * @param _target Target contract address. 35 | * @param _message Message to send to the target. 36 | * @param _gasLimit Gas limit for the provided message. 37 | */ 38 | function sendMessage( 39 | address _target, 40 | bytes calldata _message, 41 | uint32 _gasLimit 42 | ) external; 43 | } 44 | -------------------------------------------------------------------------------- /contracts/interfaces/IZkEvmMessageDelivererBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | interface IZkEvmMessageDelivererBase { 5 | /// @notice Returns the address of the caller that dispatched the message. 6 | function messageOrigin () external view returns (address); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/interfaces/IZkEvmMessageDelivererWithProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import './IZkEvmMessageDelivererBase.sol'; 5 | 6 | interface IZkEvmMessageDelivererWithProof is IZkEvmMessageDelivererBase { 7 | // TODO: move & refine this 8 | event ForeignBridgeStateImported(bytes32 indexed blockHash, bytes32 indexed stateRoot, uint256 timestamp); 9 | 10 | /// @notice Verifies the proof and executes the message. 11 | function deliverMessageWithProof ( 12 | address from, 13 | address to, 14 | uint256 value, 15 | uint256 fee, 16 | uint256 deadline, 17 | uint256 nonce, 18 | bytes calldata data, 19 | bytes calldata proof 20 | ) external; 21 | 22 | function getTimestampForStorageRoot (bytes32 storageRootHash) external view returns (uint256); 23 | 24 | function importForeignBridgeState (bytes calldata blockHeader, bytes calldata accountProof) external; 25 | } 26 | -------------------------------------------------------------------------------- /contracts/interfaces/IZkEvmMessageDispatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | interface IZkEvmMessageDispatcher { 5 | /// @notice Dispatch a message to the opposite chain. 6 | /// @param to The address of the account/contract to call and transfer `msg.value - fee`. 7 | /// @param fee Amount to be paid to the account that delivers this message on the destination chain. Deducted from `msg.value`. 8 | /// @param deadline This message is valid **before** the deadline and can be dropped **after** the deadline. In seconds since Unix Epoch. 9 | /// @param nonce A random value that can be used to avoid collisions for identical but distinct messages. Has no other purpose. 10 | /// @param data The calldata to be used when calling `to`. 11 | /// @return messageHash `keccak256(abi.encode(msg.sender, to, value, fee, deadline, nonce, data))`. 12 | /// Please note that only one message with the same hash can be dispatched at the same time. 13 | /// A message hash is not unique in the sense that it can reappear once a previous message was delivered or dropped. 14 | function dispatchMessage ( 15 | address to, 16 | uint256 fee, 17 | uint256 deadline, 18 | uint256 nonce, 19 | bytes calldata data 20 | ) external payable returns (bytes32 messageHash); 21 | 22 | /// @notice Drops a expired message and returns ETH - if any to `from`. 23 | function dropMessage ( 24 | address from, 25 | address to, 26 | uint256 value, 27 | uint256 fee, 28 | uint256 deadline, 29 | uint256 nonce, 30 | bytes calldata data, 31 | bytes calldata proof 32 | ) external; 33 | } 34 | -------------------------------------------------------------------------------- /contracts/optimism/L1OptimismBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../interfaces/ICrossDomainMessenger.sol'; 5 | import '../ZkEvmMagicNumbers.sol'; 6 | import './OptimismWrapper.sol'; 7 | 8 | contract L1OptimismBridge is ICrossDomainMessenger, ZkEvmMagicNumbers, OptimismWrapper { 9 | function sendMessage( 10 | address _target, 11 | bytes calldata _message, 12 | uint32 _gasLimit 13 | ) external { 14 | _wrapMessage(L1_BRIDGE, L2_OPTIMISM_WRAPPER, _target, _message, _gasLimit); 15 | } 16 | 17 | function relay (address from, address to, bytes calldata data) external { 18 | _relay(L1_BRIDGE, L2_OPTIMISM_WRAPPER, from, to, data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/optimism/L2OptimismBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../interfaces/ICrossDomainMessenger.sol'; 5 | import '../ZkEvmMagicNumbers.sol'; 6 | import './OptimismWrapper.sol'; 7 | 8 | contract L2OptimisimBridge is ICrossDomainMessenger, ZkEvmMagicNumbers, OptimismWrapper { 9 | function sendMessage( 10 | address _target, 11 | bytes calldata _message, 12 | uint32 _gasLimit 13 | ) external { 14 | _wrapMessage(L2_DISPATCHER, L1_OPTIMISM_WRAPPER, _target, _message, _gasLimit); 15 | } 16 | 17 | function relay (address from, address to, bytes calldata data) external { 18 | _relay(L2_DELIVERER, L1_OPTIMISM_WRAPPER, from, to, data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/optimism/OptimismWrapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../interfaces/ICrossDomainMessenger.sol'; 5 | import '../interfaces/IZkEvmMessageDispatcher.sol'; 6 | import '../interfaces/IZkEvmMessageDelivererWithProof.sol'; 7 | import '../ZkEvmUtils.sol'; 8 | 9 | abstract contract OptimismWrapper is ICrossDomainMessenger, ZkEvmUtils { 10 | address internal constant DEFAULT_XDOMAIN_SENDER = 0x000000000000000000000000000000000000dEaD; 11 | 12 | address xDomainMsgSender; 13 | 14 | function xDomainMessageSender () external view returns (address) { 15 | require( 16 | xDomainMsgSender != DEFAULT_XDOMAIN_SENDER, 17 | 'xDomainMessageSender is not set' 18 | ); 19 | return xDomainMsgSender; 20 | } 21 | 22 | function _wrapMessage ( 23 | address fromBridge, 24 | address toBridge, 25 | address _target, 26 | bytes calldata _message, 27 | uint32 _gasLimit 28 | ) internal { 29 | uint256 fee = 0; 30 | uint256 deadline = block.timestamp + 1 days; 31 | uint256 nonce; 32 | assembly { 33 | nonce := add(gas(), add(difficulty(), timestamp())) 34 | } 35 | 36 | bytes memory data = abi.encodeWithSignature('relay(address,address,bytes)', msg.sender, _target, _message); 37 | IZkEvmMessageDispatcher(fromBridge).dispatchMessage(toBridge, fee, deadline, nonce, data); 38 | 39 | emit SentMessage(_target, msg.sender, _message, nonce, _gasLimit); 40 | } 41 | 42 | function _relay (address bridge, address fromWrapper, address from, address to, bytes calldata data) internal { 43 | require(msg.sender == bridge, 'sender'); 44 | require(IZkEvmMessageDelivererWithProof(bridge).messageOrigin() == fromWrapper, 'message origin'); 45 | 46 | xDomainMsgSender = from; 47 | _callAccount(to, 0, data); 48 | xDomainMsgSender = address(0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/templates/CommonBlockOperations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract CommonBlockOperations { 5 | function _readBlockHeader ( 6 | bytes calldata blockHeader 7 | ) internal pure returns (bytes32 blockHash, bytes32 stateRoot, uint256 _timestamp) { 8 | assembly { 9 | //@INCLUDE:utils.yul 10 | //@INCLUDE:rlp.yul 11 | 12 | // expecting 16 individual items from the block header 13 | let calldataPtr, memStart, nItems, hash := decodeFlat(blockHeader.offset) 14 | 15 | // boundary check 16 | if iszero( eq(calldataPtr, add(blockHeader.offset, blockHeader.length)) ) { 17 | revertWith('BOUNDS') 18 | } 19 | if iszero( eq(nItems, 16) ) { 20 | revertWith('ITEMS') 21 | } 22 | 23 | blockHash := hash 24 | 25 | // at position 3 should be the stateRoot 26 | stateRoot := loadValue(memStart, 3) 27 | 28 | // at position 11 should be the timestamp 29 | _timestamp := loadValue(memStart, 11) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/templates/HeaderUtil.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract HeaderUtil { 5 | function _readHeaderParts ( 6 | bytes calldata blockHeader 7 | ) internal pure returns ( 8 | bytes32 parentHash, 9 | bytes32 blockHash, 10 | bytes32 stateRoot, 11 | uint256 blockNumber, 12 | uint256 blockGasUsed, 13 | uint256 blockTimestamp 14 | ) { 15 | assembly { 16 | //@INCLUDE:utils.yul 17 | //@INCLUDE:rlp.yul 18 | 19 | // expecting 16 individual items from the block header 20 | let calldataPtr, memStart, nItems, hash := decodeFlat(blockHeader.offset) 21 | require(eq(nItems, 15), "BLOCK_ITEMS") 22 | 23 | // boundary check 24 | let end := add(blockHeader.offset, blockHeader.length) 25 | require( 26 | or( 27 | lt(calldataPtr, end), 28 | eq(calldataPtr, end) 29 | ), 30 | "BOUNDS" 31 | ) 32 | 33 | blockHash := hash 34 | parentHash := loadValue(memStart, 0) 35 | stateRoot := loadValue(memStart, 3) 36 | blockNumber := loadValue(memStart, 8) 37 | blockGasUsed := loadValue(memStart, 10) 38 | blockTimestamp := loadValue(memStart, 11) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/templates/PatriciaAccountValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract PatriciaAccountValidator { 5 | function _validatePatriciaAccountProof ( 6 | address account, 7 | bytes calldata proofData 8 | ) internal pure returns (bytes32 stateRoot, bytes32 storageHash) { 9 | assembly { 10 | //@INCLUDE:rlp.yul 11 | //@INCLUDE:mpt.yul 12 | //@INCLUDE:utils.yul 13 | 14 | let ptr := proofData.offset 15 | ptr, stateRoot, storageHash := validateAccountProof(ptr, account) 16 | 17 | // the one and only boundary check 18 | // in case an attacker crafted a malicous payload 19 | // and succeeds in the prior verification steps 20 | // then this should catch any bogus accesses 21 | if iszero( eq(ptr, add(proofData.offset, proofData.length)) ) { 22 | revertWith('BOUNDS') 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/templates/PatriciaStorageValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | contract PatriciaStorageValidator { 5 | function _validatePatriciaStorageProof ( 6 | bytes32 storageKey, 7 | bytes calldata proofData 8 | ) internal pure returns (bytes32 storageRoot, bytes32 storageValue) { 9 | assembly { 10 | //@INCLUDE:rlp.yul 11 | //@INCLUDE:mpt.yul 12 | //@INCLUDE:utils.yul 13 | 14 | let ptr := proofData.offset 15 | ptr, storageRoot, storageValue := validateStorageProof(ptr, storageKey) 16 | 17 | // the one and only boundary check 18 | // in case an attacker crafted a malicous payload 19 | // and succeeds in the prior verification steps 20 | // then this should catch any bogus accesses 21 | if iszero( eq(ptr, add(proofData.offset, proofData.length)) ) { 22 | revertWith('BOUNDS') 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/templates/mpt.yul: -------------------------------------------------------------------------------- 1 | // traverses the tree from the root to the node before the leaf. 2 | // Note: `_depth` is untrusted. 3 | function walkTree (key, _ptr) -> ptr, rootHash, expectedHash, path, leafMem { 4 | ptr := _ptr 5 | 6 | // the number of distinct proofs 7 | let nNodes := byte(0, calldataload(ptr)) 8 | ptr := add(ptr, 1) 9 | 10 | // keeps track of ascend/descend - however you may look at a tree 11 | let depth 12 | 13 | for { let i := 0 } lt(i, nNodes) { i := add(i, 1) } { 14 | let memStart, nItems, hash 15 | ptr, memStart, nItems, hash := decodeFlat(ptr) 16 | 17 | // first item is considered the root node. 18 | // Otherwise verifies that the hash of the current node 19 | // is the same as the previous choosen one. 20 | switch i 21 | case 0 { 22 | rootHash := hash 23 | } default { 24 | cmp(hash, expectedHash, 'THASH') 25 | } 26 | 27 | switch nItems 28 | case 2 { 29 | // extension node 30 | let value, len 31 | 32 | // load the second item. 33 | // this is the hash of the next node (account proof only) 34 | value, len := loadValueLen(memStart, 1) 35 | expectedHash := value 36 | 37 | switch eq(i, sub(nNodes, 1)) 38 | case 0 { 39 | // get the byte length of the first item 40 | // Note: the value itself is not validated 41 | // and it is instead assumed that any invalid 42 | // value is invalidated by comparing the root hash. 43 | let prefixLen := shr(128, mload(memStart)) 44 | depth := add(depth, prefixLen) 45 | } 46 | default { 47 | leafMem := memStart 48 | } 49 | } 50 | case 17 { 51 | let bits := sub(252, mul(depth, 4)) 52 | let nibble := and(shr(bits, key), 0xf) 53 | 54 | // load the value at pos `nibble` 55 | let value, len := loadValueLen(memStart, nibble) 56 | 57 | expectedHash := value 58 | depth := add(depth, 1) 59 | } 60 | default { 61 | // everything else is unexpected 62 | revertWith('NODE') 63 | } 64 | } 65 | 66 | // lastly, derive the path of the choosen one (TM) 67 | path := derivePath(key, depth) 68 | } 69 | 70 | // Note: this doesn't work if there are no intermediate nodes before the leaf. 71 | // This is not possible in practice because of the fact that there must be at least 72 | // 2 accounts in the tree to make a transaction to a existing contract possible. 73 | // Thus, 2 leaves. 74 | function validateAccountProof (_ptr, accountAddress) -> ptr, rootHash, accountStorageHash { 75 | ptr := _ptr 76 | 77 | let encodedPath 78 | let path 79 | let hash 80 | let vlen 81 | let prevHash 82 | let leafMem 83 | let key := keccak_20(accountAddress) 84 | // `rootHash` is a return value and must be checked by the caller 85 | ptr, rootHash, prevHash, path, leafMem := walkTree(key, ptr) 86 | 87 | // 2 items 88 | // - encoded path 89 | // - account leaf RLP (4 items) 90 | require(leafMem, "ACLEAF") 91 | 92 | encodedPath := loadValue(leafMem, 0) 93 | // the calculated path must match the encoded path in the leaf 94 | cmp(path, encodedPath, 'ACROOT') 95 | 96 | // Load the position, length of the second element (RLP encoded) 97 | let leafPtr, leafLen := loadPair(leafMem, 1) 98 | let nItems 99 | leafPtr , leafMem, nItems, hash := decodeFlat(leafPtr) 100 | 101 | // the account leaf should contain 4 values, 102 | // we want: 103 | // - storageHash @ 2 104 | require(eq(nItems, 4), "ACLEAFN") 105 | accountStorageHash := loadValue(leafMem, 2) 106 | } 107 | 108 | // Supports inclusion & non-inclusion proof. 109 | function validateStorageProof (_ptr, _storageKey) -> ptr, rootHash, storageKeyValue { 110 | ptr := _ptr 111 | 112 | let encodedPath 113 | let path 114 | let hash 115 | let vlen 116 | let leafMem 117 | let key := keccak_32(_storageKey) 118 | ptr, rootHash, hash, path, leafMem := walkTree(key, ptr) 119 | 120 | switch leafMem 121 | case 0 { 122 | // assuming empty / zero storage value 123 | } 124 | default { 125 | // leaf should contain 2 values 126 | // - encoded path @ 0 127 | // - storageValue @ 1 128 | encodedPath := loadValue(leafMem, 0) 129 | storageKeyValue, vlen := loadValueLen(leafMem, 1) 130 | // Assumes that `walktTree` follows `key` 131 | let isSamePath := eq(path, encodedPath) 132 | switch isSamePath 133 | case 0 { 134 | // The proof ends with a different item. 135 | storageKeyValue := 0 136 | } 137 | default { 138 | // The calculated path matches the encoded path in the leaf. 139 | // Storage value is RLP encoded. 140 | storageKeyValue := decodeItem(storageKeyValue, vlen) 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /contracts/templates/rlp.yul: -------------------------------------------------------------------------------- 1 | // special function for decoding the storage value 2 | // because of the prefix truncation if value > 31 bytes 3 | // see `loadValue` 4 | function decodeItem (word, len) -> ret { 5 | // default 6 | ret := word 7 | 8 | // RLP single byte 9 | if lt(word, 0x80) { 10 | leave 11 | } 12 | 13 | // truncated 14 | if gt(len, 32) { 15 | leave 16 | } 17 | 18 | // value is >= 0x80 and <= 32 bytes. 19 | // `len` should be at least 2 (prefix byte + value) 20 | // otherwise the RLP is malformed. 21 | let bits := mul(len, 8) 22 | // sub 8 bits - the prefix 23 | bits := sub(bits, 8) 24 | let mask := shl(bits, 0xff) 25 | // invert the mask 26 | mask := not(mask) 27 | // should hold the value - prefix byte 28 | ret := and(ret, mask) 29 | } 30 | 31 | // returns the `len` of the whole RLP list at `ptr` 32 | // and the offset for the first value inside the list. 33 | function decodeListLength (ptr) -> len, startOffset { 34 | let firstByte := byte(0, calldataload(ptr)) 35 | 36 | // SHORT LIST 37 | // 0 - 55 bytes 38 | // 0xc0 - 0xf7 39 | if lt(firstByte, 0xf8) { 40 | len := sub(firstByte, 0xbf) 41 | startOffset := add(ptr, 1) 42 | leave 43 | } 44 | 45 | // LONG LIST 46 | // 0xf8 - 0xff 47 | // > 55 bytes 48 | { 49 | let lenOf := sub(firstByte, 0xf7) 50 | if gt(lenOf, 4) { 51 | invalid() 52 | } 53 | 54 | // load the extended length 55 | startOffset := add(ptr, 1) 56 | let extendedLen := calldataload(startOffset) 57 | let bits := sub(256, mul(lenOf, 8)) 58 | extendedLen := shr(bits, extendedLen) 59 | 60 | len := add(extendedLen, lenOf) 61 | len := add(len, 1) 62 | startOffset := add(startOffset, lenOf) 63 | leave 64 | } 65 | } 66 | 67 | // returns the calldata offset of the value and the length in bytes 68 | // for the RLP encoded data item at `ptr`. 69 | // used in `decodeFlat` 70 | function decodeValue (ptr) -> dataLen, valueOffset, isData { 71 | let firstByte := byte(0, calldataload(ptr)) 72 | 73 | // SINGLE BYTE 74 | // 0x00 - 0x7f 75 | if lt(firstByte, 0x80) { 76 | dataLen := 1 77 | valueOffset := ptr 78 | isData := 1 79 | leave 80 | } 81 | 82 | // DATA ITEM 83 | // 0 - 55 bytes long 84 | // 0x80 - 0xb7 85 | if lt(firstByte, 0xb8) { 86 | dataLen := sub(firstByte, 0x80) 87 | valueOffset := add(ptr, 1) 88 | isData := 1 89 | leave 90 | } 91 | 92 | // LONG DATA ITEM 93 | // > 55 bytes 94 | // 0xb8 - 0xbf 95 | if lt(firstByte, 0xc0) { 96 | let lenOf := sub(firstByte, 0xb7) 97 | if gt(lenOf, 4) { 98 | invalid() 99 | } 100 | 101 | // load the extended length 102 | valueOffset := add(ptr, 1) 103 | let extendedLen := calldataload(valueOffset) 104 | let bits := sub(256, mul(lenOf, 8)) 105 | extendedLen := shr(bits, extendedLen) 106 | 107 | dataLen := extendedLen 108 | valueOffset := add(valueOffset, lenOf) 109 | isData := 1 110 | leave 111 | } 112 | 113 | // SHORT LIST 114 | // 0 - 55 bytes 115 | // 0xc0 - 0xf7 116 | if lt(firstByte, 0xf8) { 117 | // intentionally ignored 118 | // dataLen := sub(firstByte, 0xbf) 119 | valueOffset := add(ptr, 1) 120 | leave 121 | } 122 | 123 | // LONG LIST 124 | // 0xf8 - 0xff 125 | // > 55 bytes 126 | { 127 | // the extended length is ignored 128 | dataLen := sub(firstByte, 0xf7) 129 | valueOffset := add(ptr, 1) 130 | leave 131 | } 132 | } 133 | 134 | // decodes all RLP encoded data and stores their DATA items 135 | // [length - 128 bits | calldata offset - 128 bits] in a continous memory region. 136 | // Expects that the RLP starts with a list that defines the length 137 | // of the whole RLP region. 138 | function decodeFlat (_ptr) -> ptr, memStart, nItems, hash { 139 | ptr := _ptr 140 | 141 | // load free memory ptr 142 | // doesn't update the ptr and leaves the memory region dirty 143 | memStart := mload(64) 144 | 145 | let payloadLen, startOffset := decodeListLength(ptr) 146 | // reuse memStart region and hash 147 | calldatacopy(memStart, ptr, payloadLen) 148 | hash := keccak256(memStart, payloadLen) 149 | 150 | let memPtr := memStart 151 | let ptrStop := add(ptr, payloadLen) 152 | ptr := startOffset 153 | 154 | // decode until the end of the list 155 | for {} lt(ptr, ptrStop) {} { 156 | let len, valuePtr, isData := decodeValue(ptr) 157 | ptr := add(len, valuePtr) 158 | 159 | if isData { 160 | // store the length of the data and the calldata offset 161 | let tmp := or(shl(128, len), valuePtr) 162 | mstore(memPtr, tmp) 163 | memPtr := add(memPtr, 32) 164 | } 165 | } 166 | 167 | if iszero(eq(ptr, ptrStop)) { 168 | invalid() 169 | } 170 | 171 | nItems := div( sub(memPtr, memStart), 32 ) 172 | } 173 | 174 | // hashes 32 bytes of `v` 175 | function keccak_32 (v) -> r { 176 | mstore(0, v) 177 | r := keccak256(0, 32) 178 | } 179 | 180 | // hashes the last 20 bytes of `v` 181 | function keccak_20 (v) -> r { 182 | mstore(0, v) 183 | r := keccak256(12, 20) 184 | } 185 | 186 | // prefix gets truncated to 256 bits 187 | // `depth` is untrusted and can lead to bogus 188 | // shifts/masks. In that case, the remaining verification 189 | // steps must fail or lead to an invalid stateRoot hash 190 | // if the proof data is 'spoofed but valid' 191 | function derivePath (key, depth) -> path { 192 | path := key 193 | 194 | let bits := mul(depth, 4) 195 | { 196 | let mask := not(0) 197 | mask := shr(bits, mask) 198 | path := and(path, mask) 199 | } 200 | 201 | // even prefix 202 | let prefix := 0x20 203 | if mod(depth, 2) { 204 | // odd 205 | prefix := 0x3 206 | } 207 | 208 | // the prefix may be shifted outside bounds 209 | // this is intended, see `loadValue` 210 | bits := sub(256, bits) 211 | prefix := shl(bits, prefix) 212 | path := or(prefix, path) 213 | } 214 | 215 | // loads and aligns a value from calldata 216 | // given the `len|offset` stored at `memPtr` 217 | function loadValue (memPtr, idx) -> value { 218 | let tmp := mload(add(memPtr, mul(32, idx))) 219 | // assuming 0xffffff is sufficient for storing calldata offset 220 | let offset := and(tmp, 0xffffff) 221 | let len := shr(128, tmp) 222 | 223 | if gt(len, 31) { 224 | // special case - truncating the value is intended. 225 | // this matches the behavior in `derivePath` that truncates to 256 bits. 226 | offset := add(offset, sub(len, 32)) 227 | value := calldataload(offset) 228 | leave 229 | } 230 | 231 | // everything else is 232 | // < 32 bytes - align the value 233 | let bits := mul( sub(32, len), 8) 234 | value := calldataload(offset) 235 | value := shr(bits, value) 236 | } 237 | 238 | // loads and aligns a value from calldata 239 | // given the `len|offset` stored at `memPtr` 240 | // Same as `loadValue` except it returns also the size 241 | // of the value. 242 | function loadValueLen (memPtr, idx) -> value, len { 243 | let tmp := mload(add(memPtr, mul(32, idx))) 244 | // assuming 0xffffff is sufficient for storing calldata offset 245 | let offset := and(tmp, 0xffffff) 246 | len := shr(128, tmp) 247 | 248 | if gt(len, 31) { 249 | // special case - truncating the value is intended. 250 | // this matches the behavior in `derivePath` that truncates to 256 bits. 251 | offset := add(offset, sub(len, 32)) 252 | value := calldataload(offset) 253 | leave 254 | } 255 | 256 | // everything else is 257 | // < 32 bytes - align the value 258 | let bits := mul( sub(32, len), 8) 259 | value := calldataload(offset) 260 | value := shr(bits, value) 261 | } 262 | 263 | function loadPair (memPtr, idx) -> offset, len { 264 | let tmp := mload(add(memPtr, mul(32, idx))) 265 | // assuming 0xffffff is sufficient for storing calldata offset 266 | offset := and(tmp, 0xffffff) 267 | len := shr(128, tmp) 268 | } 269 | -------------------------------------------------------------------------------- /contracts/templates/utils.yul: -------------------------------------------------------------------------------- 1 | // function Error(string) 2 | function revertWith (msg) { 3 | mstore(0, shl(224, 0x08c379a0)) 4 | mstore(4, 32) 5 | mstore(68, msg) 6 | let msgLen 7 | for {} msg {} { 8 | msg := shl(8, msg) 9 | msgLen := add(msgLen, 1) 10 | } 11 | mstore(36, msgLen) 12 | revert(0, 100) 13 | } 14 | 15 | function require (cond, msg) { 16 | switch cond 17 | case 0 { 18 | revertWith(msg) 19 | } 20 | } 21 | 22 | // reverts with `msg` if `a != b`. 23 | function cmp (a, b, msg) { 24 | switch eq(a, b) 25 | case 0 { 26 | revertWith(msg) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/tests/ZkEvmTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity <0.9.0; 3 | 4 | import '../generated/PatriciaAccountValidator.sol'; 5 | import '../generated/PatriciaStorageValidator.sol'; 6 | import '../generated/PublicInput.sol'; 7 | 8 | contract ZkEvmTest is PatriciaAccountValidator, PatriciaStorageValidator, PublicInput { 9 | function testPatricia ( 10 | address account, 11 | bytes32 storageKey, 12 | bytes calldata accountProof, 13 | bytes calldata storageProof 14 | ) external pure returns (bytes32 _stateRoot, bytes32 _storageValue) { 15 | (bytes32 proofStateRoot, bytes32 proofStorageRoot) = _validatePatriciaAccountProof( 16 | account, 17 | accountProof 18 | ); 19 | (bytes32 storageRoot, bytes32 storageValue) = _validatePatriciaStorageProof( 20 | storageKey, 21 | storageProof 22 | ); 23 | require(storageRoot == proofStorageRoot, 'STROOT'); 24 | 25 | return (proofStateRoot, storageValue); 26 | } 27 | 28 | function testPublicInputCommitment( 29 | uint256 MAX_TXS, 30 | uint256 MAX_CALLDATA, 31 | uint256 chainId, 32 | uint256 parentStateRoot, 33 | bytes calldata witness 34 | ) external pure returns (uint256[] memory) { 35 | uint256[] memory publicInput = 36 | _buildCommitment(MAX_TXS, MAX_CALLDATA, chainId, parentStateRoot, witness, true); 37 | 38 | return publicInput; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /coordinator/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | prover_cmd 3 | -------------------------------------------------------------------------------- /coordinator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coordinator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.0.15", features = ["derive", "env"] } 10 | env_logger = "0.9.0" 11 | ethers-core = "0.17.0" 12 | ethers-signers = "0.17.0" 13 | hyper = { version = "0.14.16", features = ["client", "server", "http1", "http2", "runtime"] } 14 | log = "0.4.14" 15 | rand = "0.8.4" 16 | serde = { version = "1.0.136", features = ["derive"] } 17 | serde_json = "1.0.78" 18 | serde_with = "2.0.1" 19 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread", "time"] } 20 | zkevm_common = { path = "../common" } 21 | 22 | [dev-dependencies] 23 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread", "time", "parking_lot"] } 24 | -------------------------------------------------------------------------------- /coordinator/src/config.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use ethers_core::types::Address; 3 | use hyper::Uri; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_with::{serde_as, DisplayFromStr}; 6 | use std::net::SocketAddr; 7 | 8 | #[serde_as] 9 | #[derive(Parser, Deserialize, Serialize, Clone, Debug)] 10 | #[clap(version, about)] 11 | /// zkEVM coordinator, coordinates between the prover and the block production and relays between the bridge contracts in L1 and L2. 12 | pub struct Config { 13 | #[clap(long, env = "COORDINATOR_RPC_SERVER_NODES")] 14 | /// Address in the form of host:port of the L2 rpc node(s). Can resolve to multiple addresses. 15 | pub rpc_server_nodes: String, 16 | 17 | #[clap(long, env = "COORDINATOR_ENABLE_FAUCET")] 18 | /// Enables faucet to send eth to L1 wallet. 19 | pub enable_faucet: bool, 20 | 21 | #[clap(long, env = "COORDINATOR_LISTEN")] 22 | /// Address for the coordinator to listen to, in the format of ip:port. 23 | pub listen: SocketAddr, 24 | 25 | #[clap(long, env = "COORDINATOR_DUMMY_PROVER")] 26 | /// Enables dummy prover, so request will not be sent to the actual prover. 27 | pub dummy_prover: bool, 28 | 29 | #[clap(long, env = "COORDINATOR_MOCK_PROVER", default_value_t = false)] 30 | /// Only use the mock prover for proof requests. 31 | pub mock_prover: bool, 32 | 33 | #[clap(long, env = "COORDINATOR_MOCK_PROVER_IF_ERROR", default_value_t = true)] 34 | /// Run the mock prover if a proof request fails. 35 | pub mock_prover_if_error: bool, 36 | 37 | #[clap(long, env = "COORDINATOR_VERIFY_PROOF", default_value_t = false)] 38 | /// Enable verification of the proof. 39 | pub verify_proof: bool, 40 | 41 | #[clap(long, env = "COORDINATOR_L1_RPC_URL")] 42 | #[serde_as(as = "DisplayFromStr")] 43 | /// L1 RPC node URL format. 44 | pub l1_rpc_url: Uri, 45 | 46 | #[clap(long, env = "COORDINATOR_L1_BRIDGE")] 47 | /// Ethereum address of the L1 bridge contract. 48 | pub l1_bridge: Address, 49 | 50 | #[clap(long, env = "COORDINATOR_L1_PRIV")] 51 | /// Private key for Ethereum L1 wallet. 52 | pub l1_priv: String, 53 | 54 | #[clap(long, env = "COORDINATOR_L2_RPC_URL")] 55 | #[serde_as(as = "DisplayFromStr")] 56 | /// L2 RPC node in http URL format. 57 | pub l2_rpc_url: Uri, 58 | 59 | #[clap(long, env = "COORDINATOR_PROVER_RPCD_URL")] 60 | #[serde_as(as = "DisplayFromStr")] 61 | /// Prover RPC node URL. 62 | pub prover_rpcd_url: Uri, 63 | 64 | #[clap(long, env = "COORDINATOR_PARAMS_PATH")] 65 | /// Parameters file or directory to use for the prover requests. 66 | /// Otherwise generates them on the fly. 67 | pub params_path: Option, 68 | 69 | #[clap(long, env = "COORDINATOR_CIRCUIT_NAME")] 70 | /// The name of the circuit to use in proof requests. 71 | /// Either "pi", "super", "evm", "state", "tx", "bytecode", "copy", "exp", "keccak" 72 | pub circuit_name: String, 73 | 74 | #[clap(long, env = "COORDINATOR_AGGREGATE_PROOF", default_value_t = false)] 75 | /// Signals the prover to aggregate the circuit proof 76 | pub aggregate_proof: bool, 77 | 78 | #[clap(long, env = "COORDINATOR_UNSAFE_RPC", default_value_t = false)] 79 | /// Allow unsafe rpc methods of the coordinator if true 80 | pub unsafe_rpc: bool, 81 | } 82 | 83 | impl Config { 84 | pub fn from_env() -> Self { 85 | Self::parse_from(std::env::args().skip(usize::MAX)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /coordinator/src/faucet.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::sync::Arc; 3 | 4 | use ethers_core::types::Address; 5 | use ethers_core::types::U256; 6 | use ethers_signers::Signer; 7 | 8 | use tokio::spawn; 9 | use tokio::sync::Mutex; 10 | 11 | use crate::shared_state::SharedState; 12 | 13 | #[derive(Clone)] 14 | pub struct Faucet { 15 | pub queue: Arc>>, 16 | } 17 | 18 | impl Default for Faucet { 19 | fn default() -> Faucet { 20 | Faucet { 21 | queue: Arc::new(Mutex::new(VecDeque::new())), 22 | } 23 | } 24 | } 25 | 26 | impl Faucet { 27 | /// Iterates over `queue` and sends ETH with the `shared_state.ro.l1_wallet`. 28 | /// To avoid replacing transactions or invoking other race conditions, 29 | /// this function should not be run in parallel with any other `SharedState` tasks. 30 | /// Only consumes up to `max_items` items from the queue each time. 31 | pub async fn drain(&self, shared_state: SharedState, max_items: usize) { 32 | let mut queue = self.queue.lock().await; 33 | let mut remaining_balance: U256 = shared_state 34 | .request_l1( 35 | "eth_getBalance", 36 | (shared_state.ro.l1_wallet.address(), "latest"), 37 | ) 38 | .await 39 | .expect("l1 balance"); 40 | 41 | // can be made configurable if needed 42 | let faucet_amount = U256::from(1000000000000000000u64); 43 | let min_wallet_balance = U256::from(1000000000000000000u64); 44 | 45 | let mut i = 0; 46 | for receiver in queue.iter().take(max_items) { 47 | log::info!("transfer of {} for {:?}", faucet_amount, receiver); 48 | 49 | if remaining_balance < faucet_amount { 50 | log::warn!( 51 | "remaining balance ({}) less than faucet amount ({})", 52 | remaining_balance, 53 | faucet_amount 54 | ); 55 | break; 56 | } 57 | if remaining_balance - faucet_amount < min_wallet_balance { 58 | log::warn!("faucet wallet balance is too low ({})", remaining_balance); 59 | break; 60 | } 61 | 62 | // spawn task to catch panics 63 | { 64 | #[allow(clippy::clone_on_copy)] 65 | let receiver = receiver.clone(); 66 | let shared_state = shared_state.clone(); 67 | let res = spawn(async move { 68 | shared_state 69 | .transaction_to_l1(Some(receiver), faucet_amount, vec![]) 70 | .await 71 | .expect("receipt"); 72 | }) 73 | .await; 74 | 75 | if let Err(err) = res { 76 | log::error!("drain: {}", err); 77 | break; 78 | } 79 | } 80 | 81 | remaining_balance -= faucet_amount; 82 | i += 1; 83 | } 84 | 85 | // drain all successful transfers 86 | queue.drain(0..i); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /coordinator/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod faucet; 3 | pub mod macros; 4 | pub mod shared_state; 5 | pub mod structs; 6 | pub mod utils; 7 | -------------------------------------------------------------------------------- /coordinator/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Wraps a expression inside an async block that timeouts after `a` ms 2 | #[macro_export] 3 | macro_rules! timeout { 4 | ($a:literal, $b:expr) => { 5 | async { 6 | let res = 7 | tokio::time::timeout(std::time::Duration::from_millis($a), async { $b }).await; 8 | 9 | if let Err(err) = &res { 10 | log::error!("timeout: {}", err); 11 | } 12 | res 13 | } 14 | .await 15 | .unwrap() 16 | }; 17 | } 18 | 19 | /// Returns `None` if env variable `a` is not set or disabled ("", "0" or "false"). 20 | /// Otherwise returns `Some(b)`. 21 | #[macro_export] 22 | macro_rules! option_enabled { 23 | ($a:literal, $b:expr) => { 24 | match var($a) { 25 | Err(_) => None, 26 | Ok(res) => match res.as_str() { 27 | "" => None, 28 | "0" => None, 29 | "false" => None, 30 | _ => Some($b), 31 | }, 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /coordinator/src/structs.rs: -------------------------------------------------------------------------------- 1 | use ethers_core::abi::encode; 2 | use ethers_core::abi::Tokenizable; 3 | use ethers_core::types::{Address, Bytes, H256, U256, U64}; 4 | use ethers_core::utils::keccak256; 5 | 6 | #[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] 7 | pub struct ForkchoiceStateV1 { 8 | #[serde(rename = "headBlockHash")] 9 | pub head_block_hash: H256, 10 | #[serde(rename = "safeBlockHash")] 11 | pub safe_block_hash: H256, 12 | #[serde(rename = "finalizedBlockHash")] 13 | pub finalized_block_hash: H256, 14 | } 15 | 16 | #[derive(Clone, Debug)] 17 | pub struct MessageBeacon { 18 | pub id: H256, 19 | pub from: Address, 20 | pub to: Address, 21 | pub value: U256, 22 | pub fee: U256, 23 | pub deadline: U256, 24 | pub nonce: U256, 25 | pub calldata: Vec, 26 | } 27 | 28 | impl MessageBeacon { 29 | /// calculates the storage address for `self` 30 | pub fn storage_slot(&self) -> H256 { 31 | let mut buf: Vec = Vec::with_capacity(64); 32 | let sig = 0x31df76a4_u32.to_be_bytes(); 33 | 34 | buf.resize(28, 0); 35 | buf.extend(sig); 36 | buf.extend(self.id.as_ref()); 37 | 38 | keccak256(buf).into() 39 | } 40 | 41 | /// Calculate message id. 42 | pub fn gen_id(&self) -> H256 { 43 | let id: H256 = keccak256(encode(&[ 44 | self.from.into_token(), 45 | self.to.into_token(), 46 | self.value.into_token(), 47 | self.fee.into_token(), 48 | self.deadline.into_token(), 49 | self.nonce.into_token(), 50 | Bytes::from(self.calldata.clone()).into_token(), 51 | ])) 52 | .into(); 53 | 54 | id 55 | } 56 | } 57 | 58 | #[derive(Debug, serde::Serialize)] 59 | pub struct SealBlockRequest<'a> { 60 | pub parent: &'a H256, 61 | pub random: &'a H256, 62 | pub timestamp: &'a U64, 63 | pub transactions: Option<&'a Vec>, 64 | } 65 | 66 | #[derive(Debug, serde::Deserialize)] 67 | pub struct BlockHeader { 68 | #[serde(rename = "parentHash")] 69 | pub parent_hash: H256, 70 | pub hash: H256, 71 | pub number: U64, 72 | #[serde(rename = "stateRoot")] 73 | pub state_root: H256, 74 | // add missing fields if required 75 | } 76 | 77 | // https://eips.ethereum.org/EIPS/eip-1186 78 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 79 | pub struct MerkleProofRequest { 80 | pub address: Address, 81 | #[serde(rename = "accountProof")] 82 | pub account_proof: Vec, 83 | pub balance: U256, 84 | #[serde(rename = "codeHash")] 85 | pub code_hash: H256, 86 | pub nonce: U256, 87 | #[serde(rename = "storageHash")] 88 | pub storage_hash: H256, 89 | #[serde(rename = "storageProof")] 90 | pub storage_proof: Vec, 91 | } 92 | 93 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 94 | pub struct StorageProof { 95 | pub key: H256, 96 | pub value: U256, 97 | pub proof: Vec, 98 | } 99 | 100 | #[derive(Debug, Clone, serde::Deserialize)] 101 | pub struct Witness { 102 | pub randomness: U256, 103 | pub input: Bytes, 104 | } 105 | -------------------------------------------------------------------------------- /coordinator/tests/chain.rs: -------------------------------------------------------------------------------- 1 | mod commitment; 2 | mod common; 3 | 4 | use crate::commitment::test_public_commitment; 5 | use crate::common::get_shared_state; 6 | use crate::common::zkevm_abi; 7 | use ethers_core::abi::encode; 8 | use ethers_core::abi::Tokenizable; 9 | use ethers_core::types::Address; 10 | use ethers_core::types::Bytes; 11 | use ethers_core::types::TransactionReceipt; 12 | use ethers_core::types::H256; 13 | use ethers_core::types::U256; 14 | use ethers_core::types::U64; 15 | use ethers_core::utils::keccak256; 16 | use ethers_signers::Signer; 17 | use rand::rngs::OsRng; 18 | use rand::Rng; 19 | use zkevm_common::json_rpc::jsonrpc_request; 20 | use zkevm_common::prover::Proofs; 21 | 22 | #[tokio::test] 23 | async fn zero_eth_transfer() { 24 | let shared_state = await_state!(); 25 | let tx_hash = shared_state 26 | .transaction_to_l2( 27 | Some(shared_state.ro.l2_wallet.address()), 28 | U256::zero(), 29 | vec![], 30 | None, 31 | ) 32 | .await 33 | .expect("tx_hash"); 34 | shared_state.mine().await; 35 | wait_for_tx!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 36 | 37 | finalize_chain!(shared_state); 38 | } 39 | 40 | #[ignore] 41 | #[tokio::test] 42 | async fn keccak() { 43 | let shared_state = await_state!(); 44 | let bytecode = vec![ 45 | 0x60, 0x0b, 0x38, 0x03, 0x80, 0x60, 0x0b, 0x3d, 0x39, 0x3d, 0xf3, 0x60, 0x01, 0x60, 0xff, 46 | 0x20, 0x00, 47 | ]; 48 | let deploy_tx_hash = shared_state 49 | .transaction_to_l2(None, U256::zero(), bytecode, None) 50 | .await 51 | .expect("tx_hash"); 52 | shared_state.mine().await; 53 | shared_state.config.lock().await.dummy_prover = true; 54 | finalize_chain!(shared_state); 55 | 56 | let deploy_receipt = wait_for_tx!(deploy_tx_hash, &shared_state.config.lock().await.l2_rpc_url); 57 | let contract_addr = deploy_receipt.contract_address; 58 | let tx_hash = shared_state 59 | .transaction_to_l2(contract_addr, U256::zero(), vec![], None) 60 | .await 61 | .expect("tx_hash"); 62 | shared_state.mine().await; 63 | wait_for_tx!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 64 | shared_state.config.lock().await.dummy_prover = false; 65 | shared_state.config.lock().await.mock_prover = true; 66 | finalize_chain!(shared_state); 67 | shared_state.config.lock().await.mock_prover = false; 68 | } 69 | 70 | #[tokio::test] 71 | async fn l1_l2_sync_test() { 72 | let abi = zkevm_abi(); 73 | let shared_state = await_state!(); 74 | let mut deposits: Vec = Vec::new(); 75 | 76 | for _ in 0..2 { 77 | let l1_bridge_addr = Some(shared_state.config.lock().await.l1_bridge); 78 | // create deposits 79 | let from = shared_state.ro.l1_wallet.address(); 80 | let to = Address::zero(); 81 | let value = U256::from(1u64); 82 | let fee = U256::zero(); 83 | let deadline = U256::from(0xffffffffffffffffu64); 84 | let nonce: U256 = rand::random::().into(); 85 | let data = Bytes::from([]); 86 | let calldata = abi 87 | .function("dispatchMessage") 88 | .unwrap() 89 | .encode_input(&[ 90 | to.into_token(), 91 | fee.into_token(), 92 | deadline.into_token(), 93 | nonce.into_token(), 94 | data.clone().into_token(), 95 | ]) 96 | .expect("calldata"); 97 | 98 | let id: H256 = keccak256(encode(&[ 99 | from.into_token(), 100 | to.into_token(), 101 | value.into_token(), 102 | fee.into_token(), 103 | deadline.into_token(), 104 | nonce.into_token(), 105 | data.into_token(), 106 | ])) 107 | .into(); 108 | deposits.push(id); 109 | 110 | // create a block with zero logs before the bridge deposit 111 | let _ = shared_state 112 | .transaction_to_l1( 113 | Some(shared_state.ro.l2_wallet.address()), 114 | U256::zero(), 115 | vec![], 116 | ) 117 | .await 118 | .expect("receipt"); 119 | // deposit 120 | shared_state 121 | .transaction_to_l1(l1_bridge_addr, value, calldata) 122 | .await 123 | .expect("receipt"); 124 | } 125 | 126 | sync!(shared_state); 127 | 128 | // verify that all deposit are picked up 129 | for id in deposits { 130 | let mut i = 0; 131 | shared_state 132 | .rw 133 | .lock() 134 | .await 135 | .l2_delivered_messages 136 | .iter() 137 | .for_each(|e| { 138 | if e == &id { 139 | i += 1 140 | } 141 | }); 142 | assert!(i == 1, "message id should exist and only once"); 143 | } 144 | 145 | finalize_chain!(shared_state); 146 | } 147 | 148 | #[ignore] 149 | #[tokio::test] 150 | async fn finalize_chain() { 151 | let shared_state = await_state!(); 152 | sync!(shared_state); 153 | shared_state.mine().await; 154 | finalize_chain!(shared_state); 155 | } 156 | 157 | // COORDINATOR_MOCK_PROVER=true ./scripts/test_prover.sh --ignored test_pi_commitment 158 | #[ignore] 159 | #[tokio::test] 160 | async fn test_pi_commitment() { 161 | let shared_state = await_state!(); 162 | sync!(shared_state); 163 | shared_state.mine().await; 164 | 165 | let mut tx_nonce: U256 = jsonrpc_request( 166 | &shared_state.config.lock().await.l2_rpc_url, 167 | "eth_getTransactionCount", 168 | (shared_state.ro.l2_wallet.address(), "latest"), 169 | ) 170 | .await 171 | .expect("nonce"); 172 | let mut txs = vec![]; 173 | for i in 0..3 { 174 | let to = if i % 2 == 0 { 175 | Some(shared_state.ro.l2_wallet.address()) 176 | } else { 177 | None 178 | }; 179 | let value = U256::from(OsRng.gen::()); 180 | let mut input: Vec = Vec::new(); 181 | while input.len() < 1234 { 182 | if to.is_none() { 183 | input.push(0); 184 | } else { 185 | input.push(OsRng.gen::()); 186 | } 187 | } 188 | txs.push(shared_state.sign_l2(to, value, tx_nonce, input).await); 189 | tx_nonce = tx_nonce + 1; 190 | } 191 | 192 | let block = shared_state 193 | .mine_block(Some(&txs)) 194 | .await 195 | .expect("mine_block"); 196 | let block_num = block.number.unwrap(); 197 | println!("{block:#?}"); 198 | 199 | loop { 200 | let proofs: Option = shared_state 201 | .request_proof(&block_num) 202 | .await 203 | .expect("proofs"); 204 | match proofs { 205 | None => continue, 206 | Some(proof) => { 207 | log::info!("{:#?}", &proof); 208 | let proof_result = proof.circuit; 209 | let table = test_public_commitment(&shared_state, &block_num, &proof.config) 210 | .await 211 | .expect("test_public_commitment"); 212 | assert_eq!(proof_result.instance, table, "public inputs"); 213 | 214 | break; 215 | } 216 | } 217 | } 218 | 219 | finalize_chain!(shared_state, true); 220 | } 221 | -------------------------------------------------------------------------------- /coordinator/tests/commitment/mod.rs: -------------------------------------------------------------------------------- 1 | use coordinator::shared_state::SharedState; 2 | use coordinator::structs::BlockHeader; 3 | use ethers_core::abi::Abi; 4 | use ethers_core::abi::AbiParser; 5 | use ethers_core::abi::Token; 6 | use ethers_core::abi::Tokenizable; 7 | use ethers_core::types::Bytes; 8 | use ethers_core::types::U256; 9 | use ethers_core::types::U64; 10 | use ethers_signers::Signer; 11 | use std::fs::File; 12 | use std::io::BufReader; 13 | use zkevm_common::prover::CircuitConfig; 14 | 15 | fn get_abi() -> Abi { 16 | AbiParser::default() 17 | .parse(&[ 18 | "function testPublicInputCommitment(uint256 MAX_TXS, uint256 MAX_CALLDATA, uint256 chainId, uint256 parentStateRoot, bytes calldata witness) returns (uint256[])", 19 | ]) 20 | .expect("parse abi") 21 | } 22 | 23 | pub(crate) async fn test_public_commitment( 24 | state: &SharedState, 25 | block_num: &U64, 26 | circuit_config: &CircuitConfig, 27 | ) -> Result, String> { 28 | let prev_block: BlockHeader = state 29 | .request_l2("eth_getHeaderByNumber", [block_num - 1]) 30 | .await 31 | .expect("prev_block"); 32 | let witness = state.request_witness(block_num).await.expect("witness"); 33 | let state_root_prev = U256::from(prev_block.state_root.as_ref()); 34 | let chain_id = state.ro.l2_wallet.chain_id(); 35 | let max_calldata = U256::from(circuit_config.max_calldata); 36 | let max_txs = U256::from(circuit_config.max_txs); 37 | 38 | let abi = get_abi(); 39 | let test_fn = abi.function("testPublicInputCommitment").unwrap(); 40 | let calldata = test_fn 41 | .encode_input(&[ 42 | max_txs.into_token(), 43 | max_calldata.into_token(), 44 | chain_id.into_token(), 45 | state_root_prev.into_token(), 46 | witness.input.into_token(), 47 | ]) 48 | .expect("calldata"); 49 | 50 | let path = "../build/contracts/combined.json"; 51 | let file = File::open(path).unwrap_or_else(|err| panic!("{}: {}", &path, err)); 52 | let reader = BufReader::new(file); 53 | let combined: serde_json::Value = serde_json::from_reader(reader).unwrap(); 54 | let bin_runtime = combined 55 | .get("contracts") 56 | .unwrap() 57 | .get("contracts/tests/ZkEvmTest.sol:ZkEvmTest") 58 | .unwrap() 59 | .get("bin-runtime") 60 | .unwrap(); 61 | 62 | let resp: Bytes = state 63 | .request_l1( 64 | "eth_call", 65 | serde_json::json!([ 66 | { 67 | "to": "0x00000000000000000000000000000000000f0000", 68 | "data": Bytes::from(calldata), 69 | }, 70 | "latest", 71 | { 72 | "0x00000000000000000000000000000000000f0000": { 73 | "code": format!("0x{}", bin_runtime.as_str().unwrap()), 74 | }, 75 | } 76 | ]), 77 | ) 78 | .await?; 79 | let mut result = test_fn.decode_output(resp.as_ref()).expect("decode output"); 80 | let table: Vec = result.pop().unwrap().into_array().unwrap(); 81 | let ret = table 82 | .iter() 83 | .map(|e| e.clone().into_uint().unwrap()) 84 | .collect(); 85 | 86 | Ok(ret) 87 | } 88 | -------------------------------------------------------------------------------- /coordinator/tests/deploy.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use crate::common::proxy_abi; 5 | use crate::common::ContractArtifact; 6 | use ethers_core::abi::Tokenizable; 7 | use ethers_core::types::Address; 8 | use ethers_core::types::Bytes; 9 | use ethers_core::types::TransactionReceipt; 10 | use ethers_core::types::U256; 11 | use ethers_core::types::U64; 12 | use std::fs::read_dir; 13 | use std::fs::File; 14 | use std::io::Read; 15 | use std::str::FromStr; 16 | 17 | macro_rules! deploy_l1 { 18 | ($DEPLOY_CODE:expr, $ADDRESS:expr) => {{ 19 | let _ = 20 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 21 | .is_test(true) 22 | .try_init(); 23 | let abi = proxy_abi(); 24 | let shared_state = await_state!(); 25 | 26 | let receipt = shared_state 27 | .transaction_to_l1(None, U256::zero(), $DEPLOY_CODE) 28 | .await 29 | .expect("receipt"); 30 | assert!(receipt.status.unwrap() == U64::from(1)); 31 | 32 | let contract_addr = receipt.contract_address.expect("contract_address"); 33 | let calldata = abi 34 | .function("upgrade") 35 | .unwrap() 36 | .encode_input(&[contract_addr.into_token()]) 37 | .expect("calldata"); 38 | 39 | let receipt = shared_state 40 | .transaction_to_l1( 41 | Some(Address::from_str($ADDRESS).unwrap()), 42 | U256::zero(), 43 | calldata, 44 | ) 45 | .await 46 | .expect("receipt"); 47 | assert!(receipt.status.unwrap() == U64::from(1)); 48 | }}; 49 | } 50 | 51 | macro_rules! deploy_l2 { 52 | ($DEPLOY_CODE:expr, $ADDRESS:expr) => {{ 53 | let _ = 54 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 55 | .is_test(true) 56 | .try_init(); 57 | let abi = proxy_abi(); 58 | let shared_state = await_state!(); 59 | 60 | let tx_hash = shared_state 61 | .transaction_to_l2(None, U256::zero(), $DEPLOY_CODE, None) 62 | .await 63 | .expect("tx_hash"); 64 | shared_state.mine().await; 65 | 66 | let receipt: TransactionReceipt = shared_state 67 | .request_l2("eth_getTransactionReceipt", [tx_hash]) 68 | .await 69 | .expect("receipt"); 70 | assert!(receipt.status.unwrap() == U64::from(1)); 71 | 72 | let contract_addr = receipt.contract_address.expect("contract_address"); 73 | let calldata = abi 74 | .function("upgrade") 75 | .unwrap() 76 | .encode_input(&[contract_addr.into_token()]) 77 | .expect("calldata"); 78 | 79 | let tx_hash = shared_state 80 | .transaction_to_l2( 81 | Some(Address::from_str($ADDRESS).unwrap()), 82 | U256::zero(), 83 | calldata, 84 | None, 85 | ) 86 | .await 87 | .expect("tx_hash"); 88 | shared_state.mine().await; 89 | 90 | let receipt: TransactionReceipt = shared_state 91 | .request_l2("eth_getTransactionReceipt", [tx_hash]) 92 | .await 93 | .expect("receipt"); 94 | assert!(receipt.status.unwrap() == U64::from(1)); 95 | }}; 96 | } 97 | 98 | macro_rules! code { 99 | ($NAME:expr) => {{ 100 | ContractArtifact::load($NAME).bin.to_vec() 101 | }}; 102 | } 103 | 104 | #[tokio::test] 105 | async fn deploy_l1_bridge() { 106 | deploy_l1!( 107 | code!("ZkEvmL1Bridge"), 108 | "0x936a70c0b28532aa22240dce21f89a8399d6ac60" 109 | ); 110 | } 111 | 112 | #[tokio::test] 113 | async fn deploy_l1_optimism() { 114 | deploy_l1!( 115 | code!("L1OptimismBridge"), 116 | "0x936a70c0b28532aa22240dce21f89a8399d6ac61" 117 | ); 118 | } 119 | 120 | // TODO: l2 gas limit not sufficient 121 | #[ignore] 122 | #[tokio::test] 123 | async fn deploy_l2_bridge() { 124 | deploy_l2!( 125 | code!("ZkEvmL2MessageDeliverer"), 126 | "0x0000000000000000000000000000000000010000" 127 | ); 128 | deploy_l2!( 129 | code!("ZkEvmL2MessageDispatcher"), 130 | "0x0000000000000000000000000000000000020000" 131 | ); 132 | } 133 | 134 | // TODO: l2 gas limit not sufficient 135 | #[ignore] 136 | #[tokio::test] 137 | async fn deploy_l2_optimism() { 138 | deploy_l2!( 139 | code!("L2OptimisimBridge"), 140 | "0x4200000000000000000000000000000000000007" 141 | ); 142 | } 143 | 144 | #[tokio::test] 145 | async fn deploy_l1_evm_verifier() { 146 | let items = read_dir("../build/contracts/plonk-verifier/"); 147 | if items.is_err() { 148 | return; 149 | } 150 | for item in items.unwrap() { 151 | let item = item.unwrap(); 152 | let path = item.path(); 153 | let file_name = item.file_name().into_string().unwrap(); 154 | let address = file_name.split('-').last().unwrap(); 155 | let mut file = File::open(&path).expect("open"); 156 | let mut deploy_code = String::from("0x600b380380600b3d393df3"); 157 | file.read_to_string(&mut deploy_code).expect("read"); 158 | println!("{path:?} {address}"); 159 | deploy_l1!(Bytes::from_str(&deploy_code).unwrap().to_vec(), address); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /coordinator/tests/hop.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use ethers_core::abi::AbiParser; 5 | use ethers_core::abi::Tokenizable; 6 | use ethers_core::types::Address; 7 | use ethers_core::types::Bytes; 8 | use ethers_core::types::TransactionReceipt; 9 | use ethers_core::types::U256; 10 | use ethers_core::types::U64; 11 | use zkevm_common::json_rpc::jsonrpc_request; 12 | 13 | #[ignore] 14 | #[tokio::test] 15 | async fn hop_deposit() { 16 | let abi = AbiParser::default() 17 | .parse(&[ 18 | // hop-protocol 19 | "function sendToL2(uint256 chainId, address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline, address relayer, uint256 relayerFee)", 20 | ]) 21 | .expect("parse abi"); 22 | 23 | let shared_state = await_state!(); 24 | 25 | // hop-protocol deposit 26 | { 27 | let hop: Address = "0xb8901acB165ed027E32754E0FFe830802919727f" 28 | .parse() 29 | .unwrap(); 30 | let chain_id = U256::from(99u64); 31 | let recipient = Address::zero(); 32 | let amount = U256::from(0x174876e8000u64); 33 | let amount_out_min = U256::from(0x173c91838du64); 34 | let deadline = U256::MAX; 35 | let relayer = Address::zero(); 36 | let relayer_fee = U256::zero(); 37 | let calldata = abi 38 | .function("sendToL2") 39 | .unwrap() 40 | .encode_input(&[ 41 | chain_id.into_token(), 42 | recipient.into_token(), 43 | amount.into_token(), 44 | amount_out_min.into_token(), 45 | deadline.into_token(), 46 | relayer.into_token(), 47 | relayer_fee.into_token(), 48 | ]) 49 | .expect("calldata"); 50 | 51 | let balance_before: U256 = jsonrpc_request( 52 | &shared_state.config.lock().await.l2_rpc_url, 53 | "eth_getBalance", 54 | (recipient, "latest"), 55 | ) 56 | .await 57 | .expect("eth_getBalance"); 58 | 59 | shared_state 60 | .transaction_to_l1(Some(hop), amount, calldata) 61 | .await 62 | .expect("receipt"); 63 | sync!(shared_state); 64 | 65 | let balance_after: U256 = jsonrpc_request( 66 | &shared_state.config.lock().await.l2_rpc_url, 67 | "eth_getBalance", 68 | (recipient, "latest"), 69 | ) 70 | .await 71 | .expect("eth_getBalance"); 72 | 73 | let min_expected_balance = balance_before + amount_out_min; 74 | assert!( 75 | balance_after >= min_expected_balance, 76 | "ETH balance after hop deposit" 77 | ); 78 | } 79 | 80 | finalize_chain!(shared_state); 81 | } 82 | 83 | #[ignore] 84 | #[tokio::test] 85 | async fn hop_cross_chain_message() { 86 | let abi = AbiParser::default() 87 | .parse(&[ 88 | // hop-protocol 89 | "function swapAndSend(uint256 chainId, address recipient, uint256 amount, uint256 bonderFee, uint256 amountOutMin, uint256 deadline, uint256 destinationAmountOutMin, uint256 destinationDeadline)", 90 | "function commitTransfers(uint256 destinationChainId)", 91 | "function chainBalance(uint256)", 92 | ]) 93 | .expect("parse abi"); 94 | let calldata = abi 95 | .function("chainBalance") 96 | .unwrap() 97 | .encode_input(&[U256::from(99u64).into_token()]) 98 | .expect("calldata"); 99 | let get_chain_balance = serde_json::json!( 100 | { 101 | "to": "0xb8901acb165ed027e32754e0ffe830802919727f", 102 | "data": Bytes::from(calldata), 103 | } 104 | ); 105 | 106 | let chain_id = U256::from(98u64); 107 | let shared_state = await_state!(); 108 | 109 | sync!(shared_state); 110 | 111 | // balance on L1 hop bridge for L2 chain 112 | let chain_balance_before: U256 = jsonrpc_request( 113 | &shared_state.config.lock().await.l1_rpc_url, 114 | "eth_call", 115 | (&get_chain_balance, "latest"), 116 | ) 117 | .await 118 | .expect("eth_call"); 119 | 120 | { 121 | // withdraw from hop 122 | let hop: Address = "0x86cA30bEF97fB651b8d866D45503684b90cb3312" 123 | .parse() 124 | .unwrap(); 125 | let recipient = Address::zero(); 126 | let amount = U256::from(0x38d7ea4c68000u64); 127 | let bonder_fee = U256::from(0x54c89e3b2703u64); 128 | let amount_out_min = U256::from(0x330b7c6533df8u64); 129 | let deadline = U256::MAX; 130 | let destination_amount_out_min = amount_out_min - bonder_fee; 131 | let destination_deadline = U256::MAX; 132 | let calldata = abi 133 | .function("swapAndSend") 134 | .unwrap() 135 | .encode_input(&[ 136 | chain_id.into_token(), 137 | recipient.into_token(), 138 | amount.into_token(), 139 | bonder_fee.into_token(), 140 | amount_out_min.into_token(), 141 | deadline.into_token(), 142 | destination_amount_out_min.into_token(), 143 | destination_deadline.into_token(), 144 | ]) 145 | .expect("calldata"); 146 | 147 | let tx_hash = shared_state 148 | .transaction_to_l2(Some(hop), amount, calldata, None) 149 | .await 150 | .expect("tx_hash"); 151 | shared_state.mine().await; 152 | wait_for_tx!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 153 | } 154 | 155 | { 156 | // commit the hop stateroot and initiate L2 > L1 message 157 | let hop: Address = "0x83f6244bd87662118d96d9a6d44f09dfff14b30e" 158 | .parse() 159 | .unwrap(); 160 | let calldata = abi 161 | .function("commitTransfers") 162 | .unwrap() 163 | .encode_input(&[chain_id.into_token()]) 164 | .expect("calldata"); 165 | let tx_hash_commit = shared_state 166 | .transaction_to_l2(Some(hop), U256::zero(), calldata, None) 167 | .await 168 | .expect("tx_hash_commit"); 169 | shared_state.mine().await; 170 | wait_for_tx!(tx_hash_commit, &shared_state.config.lock().await.l2_rpc_url); 171 | } 172 | 173 | finalize_chain!(shared_state); 174 | 175 | { 176 | // verify that the L2 > L1 message was executed successfully 177 | let chain_balance_after: U256 = jsonrpc_request( 178 | &shared_state.config.lock().await.l1_rpc_url, 179 | "eth_call", 180 | (&get_chain_balance, "latest"), 181 | ) 182 | .await 183 | .expect("eth_call"); 184 | 185 | assert!( 186 | chain_balance_before > chain_balance_after, 187 | "hop-protocol chain balance" 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /coordinator/tests/misc.rs: -------------------------------------------------------------------------------- 1 | use coordinator::shared_state::SharedState; 2 | 3 | // test for: https://github.com/privacy-scaling-explorations/zkevm-chain/issues/5 4 | #[tokio::test] 5 | async fn access_list_regression() { 6 | let shared_state = SharedState::from_env().await; 7 | shared_state.init().await; 8 | 9 | // CODESIZE 10 | // CODESIZE 11 | // SLOAD 12 | // 13 | // CODESIZE 14 | // CODESIZE 15 | // SSTORE 16 | // 17 | // RETURNDATASIZE 18 | // CODESIZE 19 | // SSTORE 20 | // 21 | // CODESIZE 22 | // CODESIZE 23 | // SLOAD 24 | // 25 | // ADDRESS 26 | // EXTCODESIZE 27 | // 28 | // RETURNDATASIZE 29 | // NOT 30 | // EXTCODESIZE 31 | // 32 | // CALLVALUE 33 | // EXTCODEHASH 34 | // 35 | // RETURNDATASIZE 36 | // RETURNDATASIZE 37 | // RETURNDATASIZE 38 | // RETURNDATASIZE 39 | // CODESIZE 40 | // CALLVALUE 41 | // GAS 42 | // CALL 43 | let req = serde_json::json!([ 44 | { 45 | "data": "0x3838543838553d3855383854303b3d193b343f3d3d3d3d38345af1", 46 | "value": "0xfafbfc", 47 | "gas": "0x2faf080", 48 | }, 49 | "latest", 50 | { 51 | "stateOverrides": { 52 | "0x0000000000000000000000000000000000000000": { 53 | "balance": "0xffffffff", 54 | }, 55 | }, 56 | }, 57 | ]); 58 | let l2: serde_json::Value = shared_state 59 | .request_l2("debug_traceCall", &req) 60 | .await 61 | .expect("should not crash"); 62 | let l1: serde_json::Value = shared_state 63 | .request_l1("debug_traceCall", &req) 64 | .await 65 | .expect("should not crash"); 66 | 67 | assert_eq!(l1, l2, "trace should be equal"); 68 | } 69 | -------------------------------------------------------------------------------- /coordinator/tests/native_deposit.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use crate::common::zkevm_abi; 5 | use coordinator::utils::*; 6 | use ethers_core::abi::encode; 7 | use ethers_core::abi::Tokenizable; 8 | use ethers_core::types::Address; 9 | use ethers_core::types::Bytes; 10 | use ethers_core::types::TransactionReceipt; 11 | use ethers_core::types::H256; 12 | use ethers_core::types::U256; 13 | use ethers_core::types::U64; 14 | use ethers_core::utils::keccak256; 15 | use ethers_signers::Signer; 16 | use zkevm_common::json_rpc::jsonrpc_request; 17 | use zkevm_common::json_rpc::jsonrpc_request_client; 18 | 19 | #[tokio::test] 20 | async fn native_deposit() { 21 | let abi = zkevm_abi(); 22 | let shared_state = await_state!(); 23 | let mut deposits: Vec = Vec::new(); 24 | let receiver = Address::zero(); 25 | let mut expected_balance: U256 = jsonrpc_request( 26 | &shared_state.config.lock().await.l2_rpc_url, 27 | "eth_getBalance", 28 | (receiver, "latest"), 29 | ) 30 | .await 31 | .expect("eth_getBalance"); 32 | let l1_bridge_addr = Some(shared_state.config.lock().await.l1_bridge); 33 | 34 | { 35 | // create deposits 36 | for _ in 0..9 { 37 | let from = shared_state.ro.l1_wallet.address(); 38 | let to = receiver; 39 | let value = U256::from(1u64); 40 | let fee = U256::zero(); 41 | let deadline = U256::from(0xffffffffffffffffu64); 42 | let nonce: U256 = rand::random::().into(); 43 | let data = Bytes::from([]); 44 | 45 | let calldata = abi 46 | .function("dispatchMessage") 47 | .unwrap() 48 | .encode_input(&[ 49 | to.into_token(), 50 | fee.into_token(), 51 | deadline.into_token(), 52 | nonce.into_token(), 53 | data.clone().into_token(), 54 | ]) 55 | .expect("calldata"); 56 | 57 | let id: H256 = keccak256(encode(&[ 58 | from.into_token(), 59 | to.into_token(), 60 | value.into_token(), 61 | fee.into_token(), 62 | deadline.into_token(), 63 | nonce.into_token(), 64 | data.into_token(), 65 | ])) 66 | .into(); 67 | 68 | deposits.push(id); 69 | expected_balance += value; 70 | shared_state 71 | .transaction_to_l1(l1_bridge_addr, value, calldata) 72 | .await 73 | .expect("receipt"); 74 | } 75 | } 76 | 77 | sync!(shared_state); 78 | 79 | // verify that all deposit are picked up 80 | { 81 | for id in deposits { 82 | let found = shared_state 83 | .rw 84 | .lock() 85 | .await 86 | .l2_delivered_messages 87 | .iter() 88 | .any(|e| e == &id); 89 | assert!(found, "message id should exist"); 90 | } 91 | 92 | sleep!(1000); 93 | let balance: U256 = jsonrpc_request( 94 | &shared_state.config.lock().await.l2_rpc_url, 95 | "eth_getBalance", 96 | (receiver, "latest"), 97 | ) 98 | .await 99 | .expect("eth_getBalance"); 100 | assert_eq!(expected_balance, balance, "ETH balance"); 101 | } 102 | 103 | finalize_chain!(shared_state); 104 | } 105 | 106 | #[tokio::test] 107 | async fn native_deposit_revert() { 108 | let abi = zkevm_abi(); 109 | let shared_state = await_state!(); 110 | let mut deposits: Vec = Vec::new(); 111 | let receiver = Address::zero(); 112 | let mut expected_balance: U256 = jsonrpc_request( 113 | &shared_state.config.lock().await.l2_rpc_url, 114 | "eth_getBalance", 115 | (receiver, "latest"), 116 | ) 117 | .await 118 | .expect("eth_getBalance"); 119 | 120 | { 121 | // create deposits 122 | let mut tx_nonce: U256 = jsonrpc_request( 123 | &shared_state.config.lock().await.l1_rpc_url, 124 | "eth_getTransactionCount", 125 | (shared_state.ro.l1_wallet.address(), "latest"), 126 | ) 127 | .await 128 | .expect("nonce"); 129 | let l1_bridge_addr = Some(shared_state.config.lock().await.l1_bridge); 130 | 131 | let mut txs: Vec = Vec::new(); 132 | for i in 0..30 { 133 | let should_revert = i % 2 == 0; 134 | let from = shared_state.ro.l1_wallet.address(); 135 | let to = match should_revert { 136 | true => shared_state.ro.l2_message_deliverer_addr, 137 | false => receiver, 138 | }; 139 | let value = U256::from(1u64); 140 | let fee = U256::zero(); 141 | let deadline = U256::from(0xffffffffffffffffu64); 142 | let nonce: U256 = rand::random::().into(); 143 | let data = Bytes::from([]); 144 | 145 | let calldata = abi 146 | .function("dispatchMessage") 147 | .unwrap() 148 | .encode_input(&[ 149 | to.into_token(), 150 | fee.into_token(), 151 | deadline.into_token(), 152 | nonce.into_token(), 153 | data.clone().into_token(), 154 | ]) 155 | .expect("calldata"); 156 | 157 | let id: H256 = keccak256(encode(&[ 158 | from.into_token(), 159 | to.into_token(), 160 | value.into_token(), 161 | fee.into_token(), 162 | deadline.into_token(), 163 | nonce.into_token(), 164 | data.into_token(), 165 | ])) 166 | .into(); 167 | 168 | deposits.push(id); 169 | if !should_revert { 170 | expected_balance += value; 171 | } 172 | 173 | txs.push( 174 | sign_transaction_l1( 175 | &shared_state.ro.http_client, 176 | &shared_state.config.lock().await.l1_rpc_url, 177 | &shared_state.ro.l1_wallet, 178 | l1_bridge_addr, 179 | value, 180 | calldata, 181 | tx_nonce, 182 | ) 183 | .await 184 | .expect("bytes"), 185 | ); 186 | 187 | tx_nonce = tx_nonce + 1; 188 | } 189 | 190 | let mut tx_hashes = Vec::new(); 191 | for raw_tx in &txs { 192 | let resp: Result = jsonrpc_request_client( 193 | RPC_REQUEST_TIMEOUT, 194 | &shared_state.ro.http_client, 195 | &shared_state.config.lock().await.l1_rpc_url, 196 | "eth_sendRawTransaction", 197 | [raw_tx], 198 | ) 199 | .await; 200 | 201 | tx_hashes.push(resp.unwrap()); 202 | } 203 | 204 | for tx_hash in tx_hashes { 205 | wait_for_tx!(tx_hash, &shared_state.config.lock().await.l1_rpc_url); 206 | } 207 | } 208 | 209 | sync!(shared_state); 210 | 211 | // verify that all valid deposits are picked up 212 | { 213 | for (i, id) in deposits.iter().enumerate() { 214 | let found = shared_state 215 | .rw 216 | .lock() 217 | .await 218 | .l2_delivered_messages 219 | .iter() 220 | .any(|e| e == id); 221 | 222 | let should_revert = i % 2 == 0; 223 | assert_eq!(should_revert, !found, "message id should exist"); 224 | } 225 | 226 | sleep!(1000); 227 | let balance: U256 = jsonrpc_request( 228 | &shared_state.config.lock().await.l2_rpc_url, 229 | "eth_getBalance", 230 | (receiver, "latest"), 231 | ) 232 | .await 233 | .expect("eth_getBalance"); 234 | assert_eq!(expected_balance, balance, "ETH balance"); 235 | } 236 | 237 | finalize_chain!(shared_state); 238 | } 239 | -------------------------------------------------------------------------------- /coordinator/tests/native_withdraw.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use crate::common::zkevm_abi; 5 | use ethers_core::abi::encode; 6 | use ethers_core::abi::Tokenizable; 7 | use ethers_core::types::Address; 8 | use ethers_core::types::Bytes; 9 | use ethers_core::types::H256; 10 | use ethers_core::types::U256; 11 | use ethers_core::utils::keccak256; 12 | use ethers_signers::Signer; 13 | use zkevm_common::json_rpc::jsonrpc_request; 14 | 15 | #[tokio::test] 16 | async fn native_withdraw() { 17 | let abi = zkevm_abi(); 18 | let shared_state = await_state!(); 19 | let mut messages: Vec = Vec::new(); 20 | let receiver = Address::zero(); 21 | let mut expected_balance: U256 = jsonrpc_request( 22 | &shared_state.config.lock().await.l1_rpc_url, 23 | "eth_getBalance", 24 | (receiver, "latest"), 25 | ) 26 | .await 27 | .expect("eth_getBalance"); 28 | 29 | shared_state.sync().await; 30 | shared_state.mine().await; 31 | 32 | { 33 | let mut tx_nonce: U256 = jsonrpc_request( 34 | &shared_state.config.lock().await.l2_rpc_url, 35 | "eth_getTransactionCount", 36 | (shared_state.ro.l2_wallet.address(), "latest"), 37 | ) 38 | .await 39 | .expect("nonce"); 40 | let mut txs = vec![]; 41 | for _ in 0..4 { 42 | let from = shared_state.ro.l2_wallet.address(); 43 | let to = receiver; 44 | let value = U256::from(1u64); 45 | let fee = U256::zero(); 46 | let deadline = U256::from(0xffffffffffffffffu64); 47 | let nonce: U256 = rand::random::().into(); 48 | let data = Bytes::from([]); 49 | let calldata = abi 50 | .function("dispatchMessage") 51 | .unwrap() 52 | .encode_input(&[ 53 | to.into_token(), 54 | fee.into_token(), 55 | deadline.into_token(), 56 | nonce.into_token(), 57 | data.clone().into_token(), 58 | ]) 59 | .expect("calldata"); 60 | 61 | let id: H256 = keccak256(encode(&[ 62 | from.into_token(), 63 | to.into_token(), 64 | value.into_token(), 65 | fee.into_token(), 66 | deadline.into_token(), 67 | nonce.into_token(), 68 | data.into_token(), 69 | ])) 70 | .into(); 71 | 72 | messages.push(id); 73 | expected_balance += value; 74 | txs.push( 75 | shared_state 76 | .sign_l2( 77 | Some(shared_state.ro.l2_message_dispatcher_addr), 78 | value, 79 | tx_nonce, 80 | calldata, 81 | ) 82 | .await, 83 | ); 84 | tx_nonce = tx_nonce + 1; 85 | } 86 | 87 | shared_state 88 | .mine_block(Some(&txs)) 89 | .await 90 | .expect("mine_block"); 91 | } 92 | 93 | finalize_chain!(shared_state); 94 | 95 | // verify that all messages are picked up 96 | { 97 | shared_state.sync().await; 98 | for id in messages { 99 | let found = shared_state 100 | .rw 101 | .lock() 102 | .await 103 | .l1_delivered_messages 104 | .iter() 105 | .any(|e| e == &id); 106 | assert!(found, "message id should exist"); 107 | } 108 | } 109 | 110 | { 111 | // check final balance on L1 112 | let balance: U256 = jsonrpc_request( 113 | &shared_state.config.lock().await.l1_rpc_url, 114 | "eth_getBalance", 115 | (receiver, "latest"), 116 | ) 117 | .await 118 | .expect("eth_getBalance"); 119 | assert_eq!(expected_balance, balance, "ETH balance"); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /coordinator/tests/patricia.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::ContractArtifact; 4 | use coordinator::shared_state::SharedState; 5 | use coordinator::structs::MerkleProofRequest; 6 | use coordinator::utils::marshal_proof_single; 7 | use ethers_core::abi::AbiParser; 8 | use ethers_core::abi::Tokenizable; 9 | use ethers_core::types::Address; 10 | use ethers_core::types::Bytes; 11 | use ethers_core::types::H256; 12 | use ethers_core::types::U256; 13 | use std::env; 14 | use std::fs::File; 15 | use std::io::BufReader; 16 | 17 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 18 | struct BlockHeader { 19 | #[serde(rename = "stateRoot")] 20 | state_root: H256, 21 | } 22 | 23 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 24 | struct TestData { 25 | block: BlockHeader, 26 | proof: MerkleProofRequest, 27 | } 28 | 29 | /// Can be used to create test-data fixtures. 30 | #[ignore] 31 | #[tokio::test] 32 | async fn patricia_fixture() { 33 | let shared_state = SharedState::from_env().await; 34 | shared_state.init().await; 35 | let addr = env::var("ADDR") 36 | .expect("ADDR env var") 37 | .parse::
() 38 | .expect("valid address"); 39 | let block: BlockHeader = shared_state 40 | .request_l2("eth_getHeaderByNumber", ["latest"]) 41 | .await 42 | .unwrap(); 43 | let slots: Vec = (0..18).map(|v| format! {"0x{v:064x}"}).collect(); 44 | let proof: MerkleProofRequest = shared_state 45 | .request_l2("eth_getProof", (addr, slots, "latest")) 46 | .await 47 | .expect("eth_getProof"); 48 | let test_data = TestData { block, proof }; 49 | let addr = format!("{addr:?}"); 50 | let file = File::create(format!("test-data-{}.json", addr.replace("0x", ""))).unwrap(); 51 | serde_json::to_writer_pretty(file, &test_data).unwrap(); 52 | } 53 | 54 | #[tokio::test] 55 | async fn patricia_validator() { 56 | let abi = AbiParser::default() 57 | .parse(&[ 58 | "function testPatricia(address account, bytes32 storageKey, bytes calldata accountProof, bytes calldata storageProof) external returns (bytes32 stateRoot, bytes32 storageValue)", 59 | ]) 60 | .expect("parse abi"); 61 | 62 | let shared_state = SharedState::from_env().await; 63 | shared_state.init().await; 64 | 65 | let mut cumulative_gas = 0; 66 | let mut samples = 0; 67 | for entry in std::fs::read_dir("tests/patricia/").unwrap() { 68 | let path = entry.expect("path").path(); 69 | let file = File::open(&path).expect("file"); 70 | let reader = BufReader::new(file); 71 | let test_data: TestData = serde_json::from_reader(reader).expect("json"); 72 | let block_header = test_data.block; 73 | let proof = test_data.proof; 74 | let account = proof.address; 75 | 76 | for storage_proof in proof.storage_proof { 77 | let storage_key = storage_proof.key; 78 | let account_proof_data: Bytes = Bytes::from(marshal_proof_single(&proof.account_proof)); 79 | let storage_proof_data: Bytes = Bytes::from(marshal_proof_single(&storage_proof.proof)); 80 | let calldata = abi 81 | .function("testPatricia") 82 | .unwrap() 83 | .encode_input(&[ 84 | account.into_token(), 85 | storage_key.into_token(), 86 | account_proof_data.into_token(), 87 | storage_proof_data.into_token(), 88 | ]) 89 | .expect("calldata"); 90 | 91 | let result = ContractArtifact::load("ZkEvmTest") 92 | .l1_trace(&Bytes::from(calldata), &shared_state) 93 | .await; 94 | assert!(result.is_ok(), "{result:?} {storage_proof:?} {path:?}"); 95 | 96 | let trace = result.unwrap(); 97 | let mut res = abi 98 | .function("testPatricia") 99 | .unwrap() 100 | .decode_output(trace.return_value.as_ref()) 101 | .expect("decode output"); 102 | let storage_value = H256::from_token(res.pop().unwrap()).expect("bytes"); 103 | let state_root = H256::from_token(res.pop().unwrap()).expect("bytes"); 104 | 105 | assert_eq!(state_root, block_header.state_root, "state_root"); 106 | assert_eq!( 107 | U256::from(storage_value.as_ref()), 108 | storage_proof.value, 109 | "storage_value" 110 | ); 111 | 112 | // remove 'tx' cost 113 | cumulative_gas += trace.gas - 21_000; 114 | samples += 1; 115 | } 116 | } 117 | 118 | let avg: u64 = cumulative_gas / samples; 119 | println!("patricia_cumulative_gas={cumulative_gas} samples={samples} avg={avg}"); 120 | 121 | const MAX_DIFF: u64 = 1000; 122 | const KNOWN_AVG: u64 = 64256; 123 | if !((KNOWN_AVG - MAX_DIFF)..=(KNOWN_AVG + MAX_DIFF)).contains(&avg) { 124 | panic!("patricia_validator: please update KNOWN_AVG ({KNOWN_AVG}), new value: {avg}"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /coordinator/tests/patricia/test-data-68b3465833fb72a70ecdf485e0e4c7bd8665fc45.json: -------------------------------------------------------------------------------- 1 | { 2 | "block": { 3 | "hash": "0x2e06ab9550d18475bc1960be5fb4e0feab74bff64fa4f285811fdc20be93481e", 4 | "number": "0xdd603d", 5 | "stateRoot": "0xa97fd4fde8b51449e2f7b3650346e41512c8c489c5e1e6b732ee70548364efeb" 6 | }, 7 | "proof": { 8 | "address": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", 9 | "accountProof": [ 10 | "0xf90211a078d28a611634f05a4a2db440db921a6c9b4cb46dd669bfb60d3949f1e6b60434a00dbce70ef3e475cc1b65a863af013827e59de99eff7a5d34b0875aeabb78e004a0f4962914fc43bda6c00a8602c873f73078005052b87b9451d9e81a62201c97e7a074b5be993053bf59d706374c5ec20bbb2a94fd670f8db115652fc009041c2f7ca0b5a2f43adcb95e6ae92f14fe6bb21842089fd2760ed3a5246fbdd189051a7b29a020d2c934926131b7f577af92e82f718d71f1595abc2db803c8c0077dca371172a0c5b7780da3baf666888990bea6bca4dbb4b5fb71adc37c9e06ce918e6583b497a0e88121f2cd39b8d74a435152679aaa07a0795343970d070bcaa894654a4eaeb8a07e1bcde37841842f92db5f1d2fdcdbb83ccc278856345b8b4ce9c18ffebddbe8a0dfaecdb2d1628d2a2b37d9c106ff10904f817301b5113b90ebbc1349c0c85995a09d479c082dc2438679df42e6bee61b1a15859fe377eec912497fb67fb75fb8d8a04b8457bfbe38e5467ea0c9d368c7cc33a3c268e5c696901e9a9c85c72bd28c09a08d5c7ebbf0b69ac6c0be38d17fd4ef538d861ac1b98fa18b61597d4669c92703a0d0dc67d453ec0007e58fad085862827139694d4f958e9d070d7d87ea20a111f8a035f016532ea5f19d0a1f7de89e7bb49ec162b21ffe6063b9c3f150083442e46da02faadee66576394af647b859362ff2aa3a8d197ff5c42ab5e4ba4ac088a56cf280", 11 | "0xf90211a0f4c9f2525f43077f7d86ebb57c1399ea983a670054248a8a603fc29a169e809ba01ae15d8ae74b23647605bf91538ebb572fafd79c33cc82c036455a093ae55a73a0a4333294109e8fab4ad287c4d5462611c8558ed24817855c43499f0106f38230a072dcabd12be05c93ac1cc05245a1d62b2d879d6c4098844cfa59b047a4eb8cdfa0072ab91331f5aec2dea7649bbd7e049e401c80d9778897999cd2f2818000c8e9a0a491804ffdf291c5541987d61fec25cb06634d16f027cc4f7c2b6ac87ae4625da00d05a8fa8b7dc01e60a7f9a730feac07fbc56e9796b9cdf62867a9be7752e076a06b3228a09126486405fd19a20090a78312e6ebd11244adbd7e476bdefb4b9a75a03722ce4aa8f4e9a009d88a31ae9c401292499a4b9cd216b7f205972e8744cc0ba0f90bf90a5aae3f1c0f7a30ee8459c7f22db6f489728f0bcb9992097477a03831a0b91c669dae336b1ef5bef137ace62a02295cb85fb5a923eaee3f638c7fc53381a063849ad20a7c705d01704034e1a88ffc46fa42a6484def0a9e1bc4c8f871a5cea02ec3b92ee5bc56212c8cb38ecce8b5d1dcd1b7066fa05afd74dd6895c6fe6121a003e0fe220f673180d46a8be21f60e6219a20906025b110c69bb19bc18234b5a7a075936e51066c4a4da82309fd4cc45dc04a2f242218a9060589ab9e15938738e0a0288cf618c91b458f4593f3a6b87945c90e39ce07a353a8b7e8abdb9a8006805f80", 12 | "0xf90211a0e2b99413bcfd6c430c1d3d0c5ee3efe04cab172d20bf78a8533c90e30f7f024ca06710d505fd81f61d51ba8bb0ecff3f6e67b5d61a0a584d800d4781fcb5882ac8a02c8b9e73752edd51943d3ec178d4ab6a15d4942c44619474237516031d02c61aa08fa3995513df73b4c87abdb284ef4702520d0321688282aff4c85ca19b00324ca0873884c4d3feaeb02b74aea25f735065a1a0342b676d39f4056530c736cd89c5a049832c4adc01a652fdb4a6f0688f30a01cc4ce7dc278f007460c82c6d4c36ff7a05cb1fb4341059eeab457e0b12a680e718ea2fb52dc996e4c1ecea3384165a009a0f79801e32dfe2f3015da3ac58f71580c50142d72f317e33a0c2205783166a494a033b5ddc2fb3ce79d8eae30d7914c07bd8820e0e1045e41cd33851199804b5f61a087ff7b561744ce17764edc97bf7ee4178c15462f412b33efb6c5770d8c14535ba0d530690600f6574ccae37cc6a64f34b6538959bca894d4be883939a509fa0e09a079a75ab5d8ef344c3be262dfb2c499552eabd0edf639b53aaf8b96f1bf88fedaa096c21f84562ab8daa1f5a1e12978e0c4d9c03735539aaacaa44085f80b5a7963a0d913ac2ae87bdb058581ff33bcc118ded9204541fdf94a29dad4b1b84d92aba3a09d7af235f5d85c25c866d0739afb38e1c572da6efa5fdd53ed26340ab3ec0adaa0d37c68224da4098a5a0e671a5e23f704909e3b9feb1567d9a0c37e19d45b6d4d80", 13 | "0xf90211a0171a2b386d318f530c3df1a756bcc8977d30db8e93bca429dd4876410ad2c969a0e4c89231ea08f991a3fe037b2adac4f068bcf096c9c936ee04f4297466c62c82a0e0a9f272ce653f57ac163cb68d0c9b76f8ec58df6e649f9ba04d0cbc4e3ffad0a09f1ed381bc2ef94cbf8f55485807b55b3b9e8e9ff13039fc4e566d25d387b125a0b71c5fbf7f8e56d2d8838a4b66761389c510eeb360134a717bf761c8a8f2e02da070b9e6b723df4d5e24312469bb1a9b4da6f2afae88bca9b5de8dd0cceb688653a0de1177f4df3b781df91f43f581f94e27b61781789264e4c83ed5f1a80524a2d1a0c4880758fcc29e0b474a1c3479896d72fb5bd4559262aedae54b9d8747dae191a0a1800f4f339e561aa4064462fd0edea52912720e6b5ee4f48a182717590f22cba024187118f5e631901a39bc74be6968b6616a6310013f8e0cbaa8bc59dde7610da0a8f89508ff4f0b66a07c645c682b34d2d4f1267e76db8c59762434e243b502e1a0851c5cb94aae99946a1e69fc62ff2e149558277ea5c34af59fd2b97842f384aaa00f93c5b560f86303d1b26b5a18c7050553f4e2d3345f52e660a0ccad4cf89c7da05598b3e6b2e45007660d38379112dcca5d3f2016ec683c06fa25470f7c24aa4aa0cc63f9285ac72c6736b4b0336954eb07c6d98adaecc77db1b600e336fa486881a025a060945fa852def7fa313e4e17a569e170bbfb585ebca491aef6b584b2ae0180", 14 | "0xf90211a0d3d818ddb3d22547ccd2eddea6f18cb61a886852d53079d373db435199f640c2a08817b02478cdc8e5106aafd20394f8887d15fa3ff5054a8a4272c648552990a9a08f3cb88dff2c42b517a26f174300cef18d1db93b2b503aeec69627730d466423a0223fd24817583f4eec1eed2bcb8bab16aa32ee61576c745a55bb96a8bbe9add4a0f6aa1c419f4133e52db4f4d86695958b68c1032e503df9f4a42858061aeb5097a029f255fe929fe835a78c356117c373fe02e571e366dcbc17c5846161683909d1a04ce5a56c928d7d0fff0dca0b1f6e5fece54f375309e4814c8eadac8be76d46a5a04ef9680ec5349d695c9a19affab6c070e95258cb0582503834f11a7e2a9c0191a009b7f2a930381077afa1eb4c5e670dac97252b9ac2d9fb203ee9bbf0f902c356a0a4f6c74f7ccaec0aa89515100ae20bc187e7ad1a4d7903577e84d59f4c125b6ba0b44377babeb18cdbb4f660cf75eaafeb1ba107d78a9d2098ed94d9237ffa819aa02b358f8439c66371769a22ad96af573043c011735eb0f384f5036cedc1b2698aa00d6ead2a7bf26ac81a0ee8d3969857982a4cb12030a3777ed7f6cc655b704108a06fc8eaa9973bfb793b841241c4cc5abdcf1ef1c825491bf644697c93b45fc8efa000dbca146c594e39ac7f9b74632a8b0315568153c371b78e55d44b4f5c504952a0c87eaf0a3cad90100b1bd655ef847e647fedfa4180b5875da84f31cd8db7b73180", 15 | "0xf90211a030150d5144c449c9baf20b9190ecd03373cf75c1e9831843c45133e4031e485ca00ef9aac4fe6dc4e486ee49dae494b247eb2d829c04e4bed1bd333f6af33999f1a05a22c84a551c933006b5489e31666904d17ee1fcc6a96d1bfc4dd1ed07b461f9a0bc1f43358a9045e2b1f15b5ecb9b6b753cb6f4a4f21202e58a0963222b941074a071903392a4b1fd43c42791b75c232b3bcb79a2c4bf213dcef263ac328b8dfb12a085e32de14238466d2831c93eed27b550ee7dcf5ea5b0f3b7bb2690aaf3ffd1bba0bae11e159d1bbb728dbbe11fc417ad26bdc84b29678e3273d18807f8c5d631d5a06711622e42be4146c38b36df1031ed659170a22df28733a88327fad6ae9c7412a074c538aeab707bd45ef692586750ef22f09870b9770eab0ef3ef99e93ac24f0da057fd50596f086136fa6af90960b8ca9f9eb5e93f4e3ea9c0d48c584bde2522cda0b29f36b18fcf14dfa827e044bbb33089e2e49dc0bf8e3b16d00dcc50dd37823ba0232089b885f024cffb338d18c3093e3ab39215fbd3a4cc70e5c8f59c42f2745da0c1f349440a107b841f95b7f90eb6be373ad5d8bcfc505b70f5e9b5056096ba11a0f624577168c9a273e73b870df278a14aa64731d6c88d0dfd71f8fac8bc91f250a0ad3eef677c5178605e3f79db12b40eb8d32fa4abd956c9aa5368f8fc62061ebca08cd4aabe820e0f1a4c9a2f392cecaad5b70d898c090189a7eae2e55dfed0e64980", 16 | "0xf8b180a0ab020129cdb0cfed67ec9a8356fb8221b4cd6155d214a7c8f5a917001e09ad5a80a099c45c51bcd00b5392b7c11f5b1b0e58aef2a73c6dec59b92efa3bb81fd5e598808080a0912873d1cb040773f8ea4fdfd47c5878865018146ddb83f0a4d1f9c5707697d18080a06ba36cdc9f472e3bfa0b4cff756393a43af1144d2fe01a13647c94ffce1e5ad080808080a03bbc3b3a41baf53302e2037c946b918c31643bef7bdcba323677aa5a701315dd80", 17 | "0xf871808080808080a05a1dee9a276a9fa4c04f7996602f2ad10a0a2d90e61db1a9d947b90e975877a8808080a045124fc2935641e85b0fca8b4386b0b5a62ade7ce4010a70ce5c29505ec14ff280808080a03ca4718f7f70123487e56aa2e5242aca37821212e16a7218766ed22e45d67c5b80", 18 | "0xf8669d208d89b54e324fc790acf237c77d2b1a3c111059ce53b4bf66e98e4663b846f8440180a0ca6f0fbdeda818216f399c395dc814121e66bca0139cef25a2b81223c438c1f6a06ec798e80f3a19de650826338677604e54d6664f44a33b53a20b22b1939f402e" 19 | ], 20 | "balance": "0x0", 21 | "codeHash": "0x6ec798e80f3a19de650826338677604e54d6664f44a33b53a20b22b1939f402e", 22 | "nonce": "0x1", 23 | "storageHash": "0xca6f0fbdeda818216f399c395dc814121e66bca0139cef25a2b81223c438c1f6", 24 | "storageProof": [ 25 | { 26 | "key": "0x0000000000000000000000000000000000000000000000000000000000000000", 27 | "value": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 28 | "proof": [ 29 | "0xf844a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 30 | ] 31 | }, 32 | { 33 | "key": "0x7da691afe6811309755d64b08db18bdc3b559237c4cc2d886875c65d6c3c9e3f", 34 | "value": "0x0", 35 | "proof": [ 36 | "0xf844a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 37 | ] 38 | } 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /coordinator/tests/worst_case.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::get_shared_state; 4 | use ethers_core::types::Address; 5 | use ethers_core::types::Block; 6 | use ethers_core::types::TransactionReceipt; 7 | use ethers_core::types::H256; 8 | use ethers_core::types::U256; 9 | use std::str::FromStr; 10 | use zkevm_common::json_rpc::jsonrpc_request; 11 | 12 | #[tokio::test] 13 | async fn worst_case_smod() { 14 | let shared_state = await_state!(); 15 | let latest_block: Block = shared_state 16 | .request_l2("eth_getBlockByNumber", ("latest", false)) 17 | .await 18 | .unwrap(); 19 | let block_gas_limit = latest_block.gas_limit; 20 | let tx_hash = shared_state 21 | .transaction_to_l2( 22 | Some(Address::from_str("0x0000000000000000000000000000000000100001").unwrap()), 23 | U256::zero(), 24 | vec![], 25 | Some(block_gas_limit), 26 | ) 27 | .await 28 | .expect("tx_hash"); 29 | shared_state.mine().await; 30 | let receipt = wait_for_tx_no_panic!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 31 | assert_eq!(receipt.gas_used.expect("gas_used"), block_gas_limit); 32 | finalize_chain!(shared_state); 33 | } 34 | 35 | #[tokio::test] 36 | async fn worst_case_mload() { 37 | let shared_state = await_state!(); 38 | let latest_block: Block = shared_state 39 | .request_l2("eth_getBlockByNumber", ("latest", false)) 40 | .await 41 | .unwrap(); 42 | let block_gas_limit = latest_block.gas_limit; 43 | let tx_hash = shared_state 44 | .transaction_to_l2( 45 | Some(Address::from_str("0x0000000000000000000000000000000000100002").unwrap()), 46 | U256::zero(), 47 | vec![], 48 | Some(block_gas_limit), 49 | ) 50 | .await 51 | .expect("tx_hash"); 52 | shared_state.mine().await; 53 | let receipt = wait_for_tx_no_panic!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 54 | assert_eq!(receipt.gas_used.expect("gas_used"), block_gas_limit); 55 | finalize_chain!(shared_state); 56 | } 57 | 58 | #[tokio::test] 59 | async fn worst_case_keccak_0_32() { 60 | let shared_state = await_state!(); 61 | let latest_block: Block = shared_state 62 | .request_l2("eth_getBlockByNumber", ("latest", false)) 63 | .await 64 | .unwrap(); 65 | let block_gas_limit = latest_block.gas_limit; 66 | let tx_hash = shared_state 67 | .transaction_to_l2( 68 | Some(Address::from_str("0x0000000000000000000000000000000000100003").unwrap()), 69 | U256::zero(), 70 | vec![], 71 | Some(block_gas_limit), 72 | ) 73 | .await 74 | .expect("tx_hash"); 75 | shared_state.mine().await; 76 | let receipt = wait_for_tx_no_panic!(tx_hash, &shared_state.config.lock().await.l2_rpc_url); 77 | assert_eq!(receipt.gas_used.expect("gas_used"), block_gas_limit); 78 | finalize_chain!(shared_state); 79 | } 80 | -------------------------------------------------------------------------------- /dev/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkevm_dev" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "main", features = ["warn-unimplemented"] } 9 | serde = { version = "1.0.136", features = ["derive"] } 10 | serde_json = { version = "1.0.78", features = ["preserve_order"] } 11 | 12 | [features] 13 | default = [] 14 | autogen = [] 15 | -------------------------------------------------------------------------------- /dev/src/bytecode.rs: -------------------------------------------------------------------------------- 1 | use crate::genesis::get_max_contract_size; 2 | 3 | #[macro_export] 4 | macro_rules! bytecode_repeat { 5 | ($code:ident, $repeat:expr, $($args:tt)*) => {{ 6 | for _ in 0..$repeat { 7 | eth_types::bytecode_internal!($code, $($args)*); 8 | } 9 | }}; 10 | 11 | ($({$repeat:expr, $($args:tt)*},)*) => {{ 12 | let mut code = eth_types::bytecode::Bytecode::default(); 13 | 14 | $( 15 | bytecode_repeat!(code, $repeat, $($args)*); 16 | )* 17 | 18 | code 19 | }}; 20 | } 21 | 22 | pub fn gen_bytecode_smod(gas_limit: usize) -> eth_types::Bytecode { 23 | let max_contract_size = get_max_contract_size(gas_limit); 24 | let fixed_bytes = 5; 25 | let iteration_size = 2; 26 | let iterations = (max_contract_size - fixed_bytes) / iteration_size; 27 | let loop_offset: usize = 1; 28 | bytecode_repeat!( 29 | // prelude 30 | { 31 | 1, 32 | GAS // gas=2 33 | JUMPDEST // gas=1 34 | }, 35 | // chain SMOD(gas, previous value) 36 | { 37 | iterations, 38 | GAS // gas=2 39 | SMOD // gas=5 40 | }, 41 | // loop with remaining gas 42 | { 43 | 1, 44 | PUSH1(loop_offset) // gas=3 45 | JUMP // gas=8 46 | }, 47 | ) 48 | } 49 | 50 | pub fn gen_bytecode_mload(gas_limit: usize) -> eth_types::Bytecode { 51 | let max_contract_size = get_max_contract_size(gas_limit); 52 | let fixed_bytes = 5; 53 | let iterations = max_contract_size - fixed_bytes; 54 | let loop_offset: usize = 1; 55 | bytecode_repeat!( 56 | // prelude 57 | { 58 | 1, 59 | CALLDATASIZE // gas=2 60 | JUMPDEST // gas=1 61 | }, 62 | // chain mload 63 | { 64 | iterations, 65 | MLOAD // gas=3 66 | }, 67 | { 68 | 1, 69 | PUSH1(loop_offset) // gas=3 70 | JUMP // gas=8 71 | }, 72 | ) 73 | } 74 | 75 | pub fn gen_bytecode_keccak_0_32(gas_limit: usize) -> eth_types::Bytecode { 76 | let max_contract_size = get_max_contract_size(gas_limit); 77 | let fixed_bytes = 6; 78 | let iteration_size = 4; 79 | let iterations = (max_contract_size - fixed_bytes) / iteration_size; 80 | let loop_offset: usize = 2; 81 | bytecode_repeat!( 82 | { 83 | 1, 84 | PUSH1(32) // gas=3 85 | JUMPDEST // gas=1 86 | }, 87 | { 88 | iterations, 89 | DUP1 // gas=3 90 | RETURNDATASIZE // gas=2 91 | SHA3 // gas=30 + 6 + (memory expansion once) 92 | POP // gas=2 93 | }, 94 | { 95 | 1, 96 | PUSH1(loop_offset) // gas=3 97 | JUMP // gas=8 98 | }, 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /dev/src/genesis.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Bytes; 2 | use serde_json::json; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | 6 | pub fn patch_genesis_l2(name: &str, address: usize, bytecode: Bytes) { 7 | let path = "../docker/geth/templates/l2-testnet.json"; 8 | let file = File::open(path).unwrap_or_else(|err| panic!("{}: {}", &path, err)); 9 | let reader = BufReader::new(&file); 10 | let mut genesis: serde_json::Value = serde_json::from_reader(reader).unwrap(); 11 | let addr = format!("{address:040x}"); 12 | genesis["alloc"][addr] = json!({ 13 | "comment": name, 14 | "balance": "0", 15 | "code": bytecode.to_string(), 16 | }); 17 | serde_json::to_writer_pretty(File::create(path).unwrap(), &genesis).expect("write"); 18 | } 19 | 20 | pub fn get_max_contract_size(gas_limit: usize) -> usize { 21 | let max_deploy_opcodes = (gas_limit - 32_000) / 16; 22 | std::cmp::max(24_576, max_deploy_opcodes) 23 | } 24 | -------------------------------------------------------------------------------- /dev/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bytecode; 2 | pub mod genesis; 3 | -------------------------------------------------------------------------------- /dev/tests/autogen.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "autogen")] 2 | 3 | use eth_types::Bytes; 4 | use zkevm_dev::bytecode::*; 5 | use zkevm_dev::genesis::patch_genesis_l2; 6 | 7 | #[test] 8 | fn autogen_genesis() { 9 | patch_genesis_l2( 10 | "worst-case smod", 11 | 0x100001, 12 | Bytes::from(gen_bytecode_smod(300_000)), 13 | ); 14 | 15 | patch_genesis_l2( 16 | "worst-case mload", 17 | 0x100002, 18 | Bytes::from(gen_bytecode_mload(300_000)), 19 | ); 20 | 21 | patch_genesis_l2( 22 | "worst-case keccak_0_32", 23 | 0x100003, 24 | Bytes::from(gen_bytecode_keccak_0_32(300_000)), 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /docker-compose-perf.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | dev: 5 | cap_add: 6 | - SYS_ADMIN 7 | environment: 8 | - DEV_PERF=true 9 | -------------------------------------------------------------------------------- /docker-compose-pub.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | networks: 4 | default: 5 | name: zkevm-chain 6 | 7 | services: 8 | leader-testnet-geth: 9 | restart: unless-stopped 10 | 11 | server-testnet-geth: 12 | restart: unless-stopped 13 | 14 | l1-testnet-geth: 15 | restart: unless-stopped 16 | 17 | bootnode: 18 | restart: unless-stopped 19 | ports: 20 | - 30303:30303 21 | 22 | dev: 23 | ports: 24 | - 8000:8000 25 | 26 | coordinator: 27 | restart: unless-stopped 28 | 29 | prover-rpcd: 30 | restart: unless-stopped 31 | 32 | web: 33 | restart: unless-stopped 34 | ports: 35 | - 8000:8000 36 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | leader-testnet-geth: 5 | build: 6 | dockerfile: docker/geth/Dockerfile 7 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 8 | volumes: 9 | - leader-testnet-geth:/root 10 | environment: 11 | - MINER_PRIV_KEY 12 | - MINER_ADDRESS 13 | - GENESIS=l2-testnet 14 | # only used to get faster initial p2p connection setup 15 | - BOOTNODE=enode://61f076e9af396ecb5a0a8fb1a2c17491c3514f2abea0c228a3fcee49395339df2008fe201b37c7c0a565b775c5f8f5389b0b3de1701dc532c951a094af841cac@bootnode:30303 16 | command: 17 | --networkid 99 18 | --nodiscover 19 | --gcmode archive 20 | --unlock $MINER_ADDRESS 21 | --password /dev/null 22 | --allow-insecure-unlock 23 | --miner.gaslimit 300000 24 | --http 25 | --http.addr "[::]" 26 | --http.port 8545 27 | --http.corsdomain=* 28 | --http.vhosts=* 29 | --http.api eth,net,web3,txpool,miner,debug 30 | --nodekeyhex abebb96d7d9bbc99730439f230afd0008c0e0cb93eafb6874fecb256572252a4 31 | 32 | bootnode: 33 | depends_on: 34 | - leader-testnet-geth 35 | build: 36 | dockerfile: docker/geth/Dockerfile 37 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 38 | volumes: 39 | - bootnode:/root 40 | environment: 41 | - MINER_ADDRESS 42 | - GENESIS=l2-testnet 43 | - BOOTNODE=enode://f28f5a7706e5aec836f3136feb7d5e7264a7f0da04ac4984f0ff2421ee1dd2b135894cf0d4f5ff8c412442b95b9bb0780a9c8a8c64de2d4a8c458586fdb20829@leader-testnet-geth:30303 44 | command: 45 | --networkid 99 46 | --syncmode full 47 | --nodiscover 48 | --gcmode archive 49 | --light.serve 100 50 | --light.nosyncserve 51 | --nodekeyhex be6e66a76b664af9debbe2f4b3b9f21257fcce34412e10dfe383aabca9b4a9c0 52 | 53 | server-testnet-geth: 54 | depends_on: 55 | - bootnode 56 | deploy: 57 | replicas: 2 58 | build: 59 | dockerfile: docker/geth/Dockerfile 60 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 61 | environment: 62 | - MINER_ADDRESS 63 | - GENESIS=l2-testnet 64 | - BOOTNODE=enode://61f076e9af396ecb5a0a8fb1a2c17491c3514f2abea0c228a3fcee49395339df2008fe201b37c7c0a565b775c5f8f5389b0b3de1701dc532c951a094af841cac@bootnode:30303 65 | command: 66 | --networkid 99 67 | --syncmode light 68 | --nodiscover 69 | --http 70 | --http.addr "[::]" 71 | --http.port 8545 72 | --http.corsdomain=* 73 | --http.vhosts=* 74 | --http.api eth,net,web3,debug 75 | 76 | l1-testnet-geth: 77 | build: 78 | dockerfile: docker/geth/Dockerfile 79 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/geth:latest 80 | volumes: 81 | - l1-testnet-geth:/root 82 | environment: 83 | - MINER_PRIV_KEY 84 | - MINER_ADDRESS 85 | - GENESIS=l1-testnet 86 | command: 87 | --networkid 98 88 | --unlock $MINER_ADDRESS 89 | --password /dev/null 90 | --allow-insecure-unlock 91 | --nodiscover 92 | --miner.gaslimit 40000000 93 | --mine 94 | --http 95 | --http.addr "[::]" 96 | --http.port 8545 97 | --http.corsdomain=* 98 | --http.vhosts=* 99 | --http.api eth,net,web3,debug 100 | --rpc.gascap 100000000000 101 | 102 | coordinator: 103 | init: true 104 | build: 105 | dockerfile: docker/coordinator/Dockerfile 106 | cache_from: 107 | - ghcr.io/privacy-scaling-explorations/zkevm-chain/coordinator-ci-cache:latest 108 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/coordinator:latest 109 | depends_on: 110 | - leader-testnet-geth 111 | - server-testnet-geth 112 | - l1-testnet-geth 113 | - prover-rpcd 114 | environment: 115 | - COORDINATOR_LISTEN=[::]:8545 116 | - COORDINATOR_RPC_SERVER_NODES=server-testnet-geth:8545 117 | - COORDINATOR_L2_RPC_URL=http://leader-testnet-geth:8545 118 | - COORDINATOR_L1_RPC_URL=http://l1-testnet-geth:8545 119 | - COORDINATOR_L1_BRIDGE=0x936a70c0b28532aa22240dce21f89a8399d6ac60 120 | - COORDINATOR_L1_PRIV=$MINER_PRIV_KEY 121 | - COORDINATOR_PROVER_RPCD_URL=http://prover-rpcd:8545 122 | - COORDINATOR_DUMMY_PROVER=${COORDINATOR_DUMMY_PROVER:-true} 123 | - COORDINATOR_ENABLE_FAUCET=true 124 | - COORDINATOR_CIRCUIT_NAME=super 125 | - COORDINATOR_UNSAFE_RPC=${COORDINATOR_UNSAFE_RPC:-false} 126 | 127 | prover-rpcd: 128 | init: true 129 | build: 130 | dockerfile: docker/prover/Dockerfile 131 | cache_from: 132 | - ghcr.io/privacy-scaling-explorations/zkevm-chain/prover-ci-cache:latest 133 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/prover:latest 134 | environment: 135 | - PROVERD_BIND=[::]:8545 136 | - PROVERD_LOOKUP=prover-rpcd:8545 137 | deploy: 138 | replicas: 1 139 | resources: 140 | limits: 141 | memory: 2500G 142 | 143 | coverage-l1: 144 | profiles: 145 | - dev 146 | image: ghcr.io/pinkiebell/develatus-apparatus:v0.4.7 147 | volumes: 148 | - .:/host:ro 149 | working_dir: /host 150 | environment: 151 | - RPC=http://l1-testnet-geth:8545 152 | 153 | coverage-l2: 154 | profiles: 155 | - dev 156 | image: ghcr.io/pinkiebell/develatus-apparatus:v0.4.7 157 | volumes: 158 | - .:/host:ro 159 | working_dir: /host 160 | environment: 161 | - RPC=http://leader-testnet-geth:8545 162 | 163 | dev: 164 | profiles: 165 | - dev 166 | depends_on: 167 | - leader-testnet-geth 168 | - server-testnet-geth 169 | - l1-testnet-geth 170 | - coverage-l1 171 | - coverage-l2 172 | build: 173 | dockerfile: docker/dev/Dockerfile 174 | cache_from: 175 | - ghcr.io/privacy-scaling-explorations/zkevm-chain/dev-ci-cache:latest 176 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/dev:latest 177 | volumes: 178 | - .:/app 179 | - dev-build-cache:/target:overlay 180 | environment: 181 | - COORDINATOR_LISTEN=[::]:8000 182 | - COORDINATOR_RPC_SERVER_NODES=server-testnet-geth:8545 183 | - COORDINATOR_L1_RPC_URL=http://l1-testnet-geth:8545 184 | - COORDINATOR_L2_RPC_URL=http://leader-testnet-geth:8545 185 | - COORDINATOR_L1_BRIDGE=0x936a70c0b28532aa22240dce21f89a8399d6ac60 186 | - COORDINATOR_L1_PRIV=$MINER_PRIV_KEY 187 | - COORDINATOR_DUMMY_PROVER=${COORDINATOR_DUMMY_PROVER:-true} 188 | - COORDINATOR_ENABLE_FAUCET=true 189 | # useful env vars if running the proverd inside the dev image 190 | - PROVERD_LOOKUP=dev:8001 191 | - COORDINATOR_PROVER_RPCD_URL=http://dev:8001 192 | - PROVERD_BIND=[::]:8001 193 | - COORDINATOR_CIRCUIT_NAME=pi 194 | - COORDINATOR_UNSAFE_RPC=true 195 | - COORDINATOR_VERIFY_PROOF=true 196 | working_dir: /app 197 | entrypoint: /sbin/getty 198 | command: '-' 199 | tty: true 200 | init: true 201 | deploy: 202 | resources: 203 | limits: 204 | memory: 2500G 205 | 206 | web: 207 | depends_on: 208 | - coordinator 209 | build: 210 | dockerfile: docker/web/Dockerfile 211 | image: ghcr.io/privacy-scaling-explorations/zkevm-chain/web:latest 212 | 213 | volumes: 214 | dev-build-cache: 215 | leader-testnet-geth: 216 | l1-testnet-geth: 217 | bootnode: 218 | -------------------------------------------------------------------------------- /docker/coordinator/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM --platform=$BUILDPLATFORM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c AS builder 4 | RUN apk add --no-cache rustup git musl-dev gcc binutils clang 5 | ENV CARGO_HOME=/usr/local/cargo 6 | ENV PATH=$CARGO_HOME/bin:$PATH 7 | ENV RUSTFLAGS='-C linker=rust-lld' 8 | ENV CC=/usr/bin/clang 9 | ENV AR=/usr/bin/ar 10 | 11 | ARG TARGETPLATFORM 12 | RUN \ 13 | case $TARGETPLATFORM in \ 14 | 'linux/amd64') arch=x86_64 ;; \ 15 | 'linux/arm64') arch=aarch64 ;; \ 16 | esac; \ 17 | printf "$arch-unknown-linux-musl" > /tmp/target; 18 | 19 | WORKDIR /target/src 20 | COPY rust-toolchain . 21 | RUN rustup-init -y --no-modify-path --profile minimal --default-toolchain $(cat rust-toolchain) --target $(cat /tmp/target) 22 | # trigger fetch of crates index 23 | RUN cargo search --limit 0 24 | 25 | COPY . . 26 | RUN cargo build --locked --bin coordinator --release --target-dir /target --target $(cat /tmp/target) && \ 27 | mv /target/*-unknown-linux-musl/release/coordinator / && rm -rf /target 28 | 29 | FROM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c 30 | COPY --from=builder /coordinator / 31 | ENTRYPOINT ["/coordinator"] 32 | -------------------------------------------------------------------------------- /docker/dev/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM alpine:3.15 AS solc 4 | RUN apk update && apk add boost-dev boost-static build-base cmake git 5 | ARG SOLC_VERSION="0.8.18" 6 | RUN git clone --depth 1 -b v"${SOLC_VERSION}" https://github.com/ethereum/solidity.git 7 | WORKDIR solidity/ 8 | RUN \ 9 | touch prerelease.txt && \ 10 | cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSOLC_LINK_STATIC=1 && \ 11 | make -j$(nproc) solc && \ 12 | strip solc/solc && \ 13 | mv solc/solc /solc && \ 14 | rm -rf $(pwd) 15 | 16 | # developer image 17 | # Should be alpine like the production images but fails due to 18 | # linkage bug(segfaults of test binaries w/ linked golang code) in rust. 19 | # Use debian until this is resolved. 20 | FROM debian:bookworm-slim 21 | RUN set -eux; \ 22 | apt-get update; \ 23 | apt-get install -y --no-install-recommends \ 24 | ca-certificates \ 25 | gcc \ 26 | libc6-dev \ 27 | wget \ 28 | golang \ 29 | pkg-config \ 30 | liblzma-dev \ 31 | procps \ 32 | jq \ 33 | curl \ 34 | git \ 35 | xz-utils \ 36 | time \ 37 | linux-perf \ 38 | libfontconfig-dev 39 | ENV CARGO_TARGET_DIR=/target 40 | ENV CARGO_HOME=/usr/local/cargo 41 | ENV PATH=$CARGO_HOME/bin:$PATH 42 | COPY --link --chmod=444 rust-toolchain /tmp/rust-toolchain 43 | # adapted from official docker-rust 44 | RUN set -eux; \ 45 | dpkgArch="$(dpkg --print-architecture)"; \ 46 | case "${dpkgArch##*-}" in \ 47 | amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='bb31eaf643926b2ee9f4d8d6fc0e2835e03c0a60f34d324048aa194f0b29a71c' ;; \ 48 | armhf) rustArch='armv7-unknown-linux-gnueabihf'; rustupSha256='6626b90205d7fe7058754c8e993b7efd91dedc6833a11a225b296b7c2941194f' ;; \ 49 | arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='4ccaa7de6b8be1569f6b764acc28e84f5eca342f5162cd5c810891bff7ed7f74' ;; \ 50 | i386) rustArch='i686-unknown-linux-gnu'; rustupSha256='34392b53a25c56435b411d3e575b63aab962034dd1409ba405e708610c829607' ;; \ 51 | *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \ 52 | esac; \ 53 | url="https://static.rust-lang.org/rustup/archive/1.25.2/${rustArch}/rustup-init"; \ 54 | wget "$url"; \ 55 | echo "${rustupSha256} *rustup-init" | sha256sum -c -; \ 56 | chmod +x rustup-init; \ 57 | ./rustup-init -y --no-modify-path --profile default --default-toolchain $(cat /tmp/rust-toolchain) --default-host ${rustArch}; \ 58 | rm rustup-init; 59 | # set default home to global cache directory 60 | ENV CARGO_HOME=/target/cargo 61 | COPY --from=solc /solc /usr/bin/solc 62 | -------------------------------------------------------------------------------- /docker/geth/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM ghcr.io/privacy-scaling-explorations/go-ethereum:v1.10.23-zkevm 4 | COPY docker/geth/init.sh /init.sh 5 | COPY docker/geth/templates /templates 6 | ENTRYPOINT ["/init.sh"] 7 | -------------------------------------------------------------------------------- /docker/geth/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | DEFAULT_GETH_ARGS='' 6 | GENESIS_GENERATED='/root/genesis.json' 7 | GENESIS_TEMPLATE="/templates/$GENESIS.json" 8 | 9 | if [[ ! -e /root/.ethereum/geth ]]; then 10 | echo 'init chain' 11 | cat "$GENESIS_TEMPLATE" | sed "s/MINER_ADDRESS/$MINER_ADDRESS/g" > $GENESIS_GENERATED 12 | geth $DEFAULT_GETH_ARGS init $GENESIS_GENERATED 13 | fi 14 | 15 | if [[ ! -z $MINER_PRIV_KEY ]]; then 16 | geth $DEFAULT_GETH_ARGS --exec 'try { personal.importRawKey("'$MINER_PRIV_KEY'", null) } catch (e) { if (e.message !== "account already exists") { throw e; } }' console 17 | fi 18 | 19 | if [[ ! -z $BOOTNODE ]]; then 20 | cat > /geth.toml << EOF 21 | [Node.P2P] 22 | BootstrapNodes = ["$BOOTNODE"] 23 | StaticNodes = ["$BOOTNODE"] 24 | EOF 25 | 26 | DEFAULT_GETH_ARGS="$DEFAULT_GETH_ARGS --config /geth.toml" 27 | fi 28 | 29 | exec geth $DEFAULT_GETH_ARGS $@ 30 | -------------------------------------------------------------------------------- /docker/prover/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM --platform=$BUILDPLATFORM alpine@sha256:02bb6f428431fbc2809c5d1b41eab5a68350194fb508869a33cb1af4444c9b11 AS builder 4 | RUN apk add --no-cache rustup git musl-dev gcc binutils clang go 5 | ENV CARGO_HOME=/usr/local/cargo 6 | ENV PATH=$CARGO_HOME/bin:$PATH 7 | ENV RUSTFLAGS='-C linker=rust-lld' 8 | ENV CC=/usr/bin/clang 9 | ENV AR=/usr/bin/ar 10 | 11 | ARG TARGETPLATFORM 12 | RUN \ 13 | case $TARGETPLATFORM in \ 14 | 'linux/amd64') arch=x86_64 ;; \ 15 | 'linux/arm64') arch=aarch64 ;; \ 16 | esac; \ 17 | printf "$arch-unknown-linux-musl" > /tmp/target; 18 | 19 | WORKDIR /target/src 20 | COPY rust-toolchain . 21 | RUN rustup-init -y --no-modify-path --profile minimal --default-toolchain $(cat rust-toolchain) --target $(cat /tmp/target) 22 | # trigger fetch of crates index 23 | RUN cargo search --limit 0 24 | 25 | COPY . . 26 | RUN cargo build --locked --bin prover_rpcd --release --target-dir /target --target $(cat /tmp/target) && \ 27 | mv /target/*-unknown-linux-musl/release/prover_rpcd / && rm -rf /target 28 | 29 | FROM alpine@sha256:686d8c9dfa6f3ccfc8230bc3178d23f84eeaf7e457f36f271ab1acc53015037c 30 | ENTRYPOINT ["/prover_rpcd"] 31 | COPY --from=builder /prover_rpcd / 32 | -------------------------------------------------------------------------------- /docker/web/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # https://github.com/privacy-scaling-explorations/hop/commit/e09a43ca270d4d0c7be63751c95ad037f1f68723 4 | FROM ghcr.io/privacy-scaling-explorations/hop/hop-frontend@sha256:cf8ef7061033fb2d8f349523baaee4b62859799e7e00d5be103023b0107b2b58 AS hop 5 | # nginx 1.21.6 6 | FROM nginx@sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514 7 | COPY --from=hop /www /www 8 | COPY docker/web/nginx.conf /etc/nginx/nginx.conf 9 | -------------------------------------------------------------------------------- /docker/web/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 4096; 6 | } 7 | 8 | http { 9 | include /etc/nginx/mime.types; 10 | default_type application/octet-stream; 11 | index index.html; 12 | 13 | sendfile on; 14 | tcp_nopush on; 15 | keepalive_timeout 65; 16 | gzip_types text/plain text/html text/css text/xml image/svg+xml application/javascript application/json; 17 | 18 | server { 19 | listen 8000; 20 | absolute_redirect off; 21 | 22 | location / { 23 | # no brotli? 24 | gzip on; 25 | root /www/; 26 | try_files $uri /index.html; 27 | } 28 | 29 | location = /rpc/l1 { 30 | proxy_pass http://l1-testnet-geth:8545/; 31 | } 32 | 33 | location = /rpc/l2 { 34 | proxy_pass http://coordinator:8545/; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /prover/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prover" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_04_20" } 9 | bus-mapping = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "main", features = ["test"] } 10 | eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "main", features = ["warn-unimplemented"] } 11 | zkevm-circuits = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "main", features = ["test-circuits"] } 12 | snark-verifier = { git = "https://github.com/privacy-scaling-explorations/snark-verifier.git", tag = "v2023_04_20", default-features = false, features = ["loader_evm", "parallel"] } 13 | env_logger = "0.9.0" 14 | ethers-providers = "0.17.0" 15 | hyper = { version = "0.14.16", features = ["server"] } 16 | rand_xorshift = "0.3" 17 | log = "0.4.14" 18 | rand = "0.8.4" 19 | serde = { version = "1.0.136", features = ["derive"] } 20 | serde_json = "1.0.78" 21 | strum = "0.24" 22 | tokio = { version = "1.16.1", features = ["macros", "rt-multi-thread"] } 23 | zkevm_common = { path = "../common" } 24 | itertools = "0.10.3" 25 | clap = { version = "4.0.14", features = ["derive", "env"] } 26 | 27 | # autogen 28 | mock = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", branch = "main", optional = true } 29 | ethers-signers = { version = "0.17.0", optional = true } 30 | zkevm_dev = { path = "../dev", optional = true } 31 | paste = { version = "1.0.11", optional = true } 32 | 33 | [features] 34 | default = [] 35 | autogen = ["mock", "ethers-signers", "zkevm_dev", "paste"] 36 | -------------------------------------------------------------------------------- /prover/build.rs: -------------------------------------------------------------------------------- 1 | use std::env::var; 2 | use std::process::Command; 3 | 4 | fn run(cmd: &str, args: Vec<&str>) -> String { 5 | let err = format!("Error running: {} {:#?}", cmd, &args); 6 | let result = Command::new(cmd).args(&args).output().expect(&err); 7 | assert!(result.status.success(), "Command failed: {err}"); 8 | 9 | String::from_utf8(result.stdout).expect("utf8 output") 10 | } 11 | 12 | fn get_crate_version(pkg: &str) -> String { 13 | run( 14 | "cargo", 15 | vec![ 16 | "tree", 17 | "--package", 18 | pkg, 19 | "--depth", 20 | "0", 21 | "--prefix", 22 | "none", 23 | "--quiet", 24 | "--charset", 25 | "utf8", 26 | ], 27 | ) 28 | } 29 | 30 | fn main() { 31 | let pkg_version = var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION"); 32 | let version = format!( 33 | "{} {} {}", 34 | pkg_version, 35 | run( 36 | "git", 37 | vec![ 38 | "-c", 39 | "safe.directory=*", 40 | "describe", 41 | "--all", 42 | "--long", 43 | "--dirty" 44 | ] 45 | ), 46 | get_crate_version("zkevm-circuits"), 47 | ); 48 | println!( 49 | "cargo:rustc-env=PROVER_VERSION={}", 50 | version.replace('\n', "") 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /prover/src/bin/gen_params.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::poly::commitment::Params; 2 | use prover::ProverParams; 3 | use rand::rngs::OsRng; 4 | use std::env; 5 | use std::fs::File; 6 | use std::io::Write; 7 | 8 | /// This utility supports parameter generation. 9 | /// Can be invoked with: gen_params 10 | fn main() { 11 | let mut args = env::args(); 12 | let params_path: String = args.next_back().expect("path to file"); 13 | let degree: u32 = args 14 | .next_back() 15 | .expect("degree") 16 | .parse::() 17 | .expect("valid number"); 18 | let mut file = File::create(¶ms_path).expect("Failed to create file"); 19 | 20 | println!("Generating params with degree: {degree}"); 21 | 22 | let general_params = ProverParams::setup(degree, OsRng); 23 | let mut buf = Vec::new(); 24 | general_params 25 | .write(&mut buf) 26 | .expect("Failed to write params"); 27 | file.write_all(&buf[..]) 28 | .expect("Failed to write params to file"); 29 | 30 | println!("Written to {params_path}"); 31 | } 32 | -------------------------------------------------------------------------------- /prover/src/bin/prover_cmd.rs: -------------------------------------------------------------------------------- 1 | use env_logger::Env; 2 | use prover::shared_state::SharedState; 3 | use std::env::var; 4 | use zkevm_common::prover::*; 5 | 6 | /// This command generates and prints the proofs to stdout. 7 | /// Required environment variables: 8 | /// - PROVERD_BLOCK_NUM - the block number to generate the proof for 9 | /// - PROVERD_RPC_URL - a geth http rpc that supports the debug namespace 10 | /// - PROVERD_PARAMS_PATH - a path to a file generated with the gen_params tool 11 | #[tokio::main] 12 | async fn main() { 13 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 14 | 15 | let block_num: u64 = var("PROVERD_BLOCK_NUM") 16 | .expect("PROVERD_BLOCK_NUM env var") 17 | .parse() 18 | .expect("Cannot parse PROVERD_BLOCK_NUM env var"); 19 | let rpc_url: String = var("PROVERD_RPC_URL") 20 | .expect("PROVERD_RPC_URL env var") 21 | .parse() 22 | .expect("Cannot parse PROVERD_RPC_URL env var"); 23 | let params_path: String = var("PROVERD_PARAMS_PATH") 24 | .expect("PROVERD_PARAMS_PATH env var") 25 | .parse() 26 | .expect("Cannot parse PROVERD_PARAMS_PATH env var"); 27 | 28 | let state = SharedState::new(String::new(), None); 29 | let request = ProofRequestOptions { 30 | circuit: "super".to_string(), 31 | block: block_num, 32 | rpc: rpc_url, 33 | retry: false, 34 | param: Some(params_path), 35 | mock: false, 36 | aggregate: false, 37 | ..Default::default() 38 | }; 39 | 40 | state.get_or_enqueue(&request).await; 41 | state.duty_cycle().await; 42 | let result = state 43 | .get_or_enqueue(&request) 44 | .await 45 | .expect("some") 46 | .expect("result"); 47 | 48 | serde_json::to_writer(std::io::stdout(), &result).expect("serialize and write"); 49 | } 50 | -------------------------------------------------------------------------------- /prover/src/bin/prover_rpcd.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use env_logger::Env; 3 | 4 | use prover::server::serve; 5 | use prover::shared_state::SharedState; 6 | use prover::VERSION; 7 | 8 | #[derive(Parser, Debug)] 9 | #[clap(version = VERSION, about)] 10 | /// This command starts a http/json-rpc server and serves proof oriented methods. 11 | pub(crate) struct ProverdConfig { 12 | #[clap(long, env = "PROVERD_BIND")] 13 | /// The interface address + port combination to accept connections on, 14 | /// e.g. `[::]:1234`. 15 | bind: String, 16 | #[clap(long, env = "PROVERD_LOOKUP")] 17 | /// A `HOSTNAME:PORT` conformant string that will be used for DNS service discovery of other nodes. 18 | lookup: Option, 19 | } 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | let config = ProverdConfig::parse(); 24 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 25 | 26 | let shared_state = SharedState::new(SharedState::random_worker_id(), config.lookup); 27 | { 28 | // start the http server 29 | let h1 = serve(&shared_state, &config.bind); 30 | 31 | // starts the duty cycle loop 32 | let ctx = shared_state.clone(); 33 | // use a dedicated runtime for mixed async / heavy (blocking) compute 34 | let rt = tokio::runtime::Builder::new_multi_thread() 35 | .enable_all() 36 | .build() 37 | .unwrap(); 38 | let h2 = rt.spawn(async move { 39 | loop { 40 | let ctx = ctx.clone(); 41 | // enclose this call to catch panics which may 42 | // occur due to network services 43 | let _ = tokio::spawn(async move { 44 | log::debug!("task: duty_cycle"); 45 | ctx.duty_cycle().await; 46 | }) 47 | .await; 48 | tokio::time::sleep(std::time::Duration::from_millis(1000)).await; 49 | } 50 | }); 51 | 52 | // this task loop makes sure to merge task results periodically 53 | // even if this instance is busy with proving 54 | let ctx = shared_state.clone(); 55 | let h3 = tokio::spawn(async move { 56 | loop { 57 | let ctx = ctx.clone(); 58 | // enclose this call to catch panics which may 59 | // occur due to network services 60 | let _ = tokio::spawn(async move { 61 | log::debug!("task: merge_tasks_from_peers"); 62 | let _ = ctx.merge_tasks_from_peers().await; 63 | }) 64 | .await; 65 | tokio::time::sleep(std::time::Duration::from_millis(1000)).await; 66 | } 67 | }); 68 | 69 | // wait for all tasks 70 | if tokio::try_join!(h1, h2, h3).is_err() { 71 | panic!("unexpected task error"); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /prover/src/circuit_autogen.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! match_circuit_params { 3 | ($gas_used:expr, $on_match:expr, $on_error:expr) => { 4 | match $gas_used { 5 | 0..=63000 => { 6 | const CIRCUIT_CONFIG: CircuitConfig = CircuitConfig { 7 | block_gas_limit: 63000, 8 | max_txs: 3, 9 | max_calldata: 10500, 10 | max_bytecode: 24634, 11 | max_rws: 476052, 12 | max_copy_rows: 896002, 13 | max_exp_steps: 4200, 14 | min_k: 20, 15 | pad_to: 476052, 16 | min_k_aggregation: 26, 17 | keccak_padding: 336000, 18 | }; 19 | $on_match 20 | } 21 | 63001..=300000 => { 22 | const CIRCUIT_CONFIG: CircuitConfig = CircuitConfig { 23 | block_gas_limit: 300000, 24 | max_txs: 14, 25 | max_calldata: 69750, 26 | max_bytecode: 139500, 27 | max_rws: 3161966, 28 | max_copy_rows: 5952002, 29 | max_exp_steps: 27900, 30 | min_k: 23, 31 | pad_to: 3161966, 32 | min_k_aggregation: 26, 33 | keccak_padding: 1600000, 34 | }; 35 | $on_match 36 | } 37 | 38 | _ => $on_error, 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /prover/src/circuit_witness.rs: -------------------------------------------------------------------------------- 1 | use crate::Fr; 2 | use bus_mapping::circuit_input_builder::BuilderClient; 3 | use bus_mapping::circuit_input_builder::CircuitsParams; 4 | use bus_mapping::mock::BlockData; 5 | use bus_mapping::rpc::GethClient; 6 | use eth_types::geth_types; 7 | use eth_types::geth_types::GethData; 8 | use eth_types::Address; 9 | use eth_types::ToBigEndian; 10 | use eth_types::Word; 11 | use eth_types::H256; 12 | use ethers_providers::Http; 13 | use std::str::FromStr; 14 | use zkevm_circuits::evm_circuit; 15 | use zkevm_circuits::pi_circuit::PublicData; 16 | use zkevm_common::prover::CircuitConfig; 17 | 18 | /// Wrapper struct for circuit witness data. 19 | pub struct CircuitWitness { 20 | pub circuit_config: CircuitConfig, 21 | pub eth_block: eth_types::Block, 22 | pub block: bus_mapping::circuit_input_builder::Block, 23 | pub code_db: bus_mapping::state_db::CodeDB, 24 | } 25 | 26 | impl CircuitWitness { 27 | pub fn dummy(circuit_config: CircuitConfig) -> Result { 28 | let history_hashes = vec![Word::zero(); 256]; 29 | let mut eth_block: eth_types::Block = eth_types::Block::default(); 30 | eth_block.author = Some(Address::zero()); 31 | eth_block.number = Some(history_hashes.len().into()); 32 | eth_block.base_fee_per_gas = Some(0.into()); 33 | eth_block.hash = Some(eth_block.parent_hash); 34 | eth_block.gas_limit = circuit_config.block_gas_limit.into(); 35 | 36 | let circuit_params = CircuitsParams { 37 | max_txs: circuit_config.max_txs, 38 | max_calldata: circuit_config.max_calldata, 39 | max_bytecode: circuit_config.max_bytecode, 40 | max_rws: circuit_config.max_rws, 41 | max_copy_rows: circuit_config.max_copy_rows, 42 | max_exp_steps: circuit_config.max_exp_steps, 43 | max_evm_rows: circuit_config.pad_to, 44 | max_keccak_rows: circuit_config.keccak_padding, 45 | }; 46 | let empty_data = GethData { 47 | chain_id: Word::from(99), 48 | history_hashes: vec![Word::zero(); 256], 49 | eth_block, 50 | geth_traces: Vec::new(), 51 | accounts: Vec::new(), 52 | }; 53 | let mut builder = 54 | BlockData::new_from_geth_data_with_params(empty_data.clone(), circuit_params) 55 | .new_circuit_input_builder(); 56 | builder 57 | .handle_block(&empty_data.eth_block, &empty_data.geth_traces) 58 | .unwrap(); 59 | Ok(Self { 60 | circuit_config, 61 | eth_block: empty_data.eth_block, 62 | block: builder.block, 63 | code_db: builder.code_db, 64 | }) 65 | } 66 | 67 | /// Gathers debug trace(s) from `rpc_url` for block `block_num`. 68 | /// Expects a go-ethereum node with debug & archive capabilities on `rpc_url`. 69 | pub async fn from_rpc( 70 | block_num: &u64, 71 | rpc_url: &str, 72 | ) -> Result> { 73 | let url = Http::from_str(rpc_url)?; 74 | let geth_client = GethClient::new(url); 75 | // TODO: add support for `eth_getHeaderByNumber` 76 | let block = geth_client.get_block_by_number((*block_num).into()).await?; 77 | let circuit_config = 78 | crate::match_circuit_params!(block.gas_used.as_usize(), CIRCUIT_CONFIG, { 79 | return Err(format!( 80 | "No circuit parameters found for block with gas used={}", 81 | block.gas_used 82 | ) 83 | .into()); 84 | }); 85 | let circuit_params = CircuitsParams { 86 | max_txs: circuit_config.max_txs, 87 | max_calldata: circuit_config.max_calldata, 88 | max_bytecode: circuit_config.max_bytecode, 89 | max_rws: circuit_config.max_rws, 90 | max_copy_rows: circuit_config.max_copy_rows, 91 | max_exp_steps: circuit_config.max_exp_steps, 92 | max_evm_rows: circuit_config.pad_to, 93 | max_keccak_rows: circuit_config.keccak_padding, 94 | }; 95 | let builder = BuilderClient::new(geth_client, circuit_params).await?; 96 | let (builder, eth_block) = builder.gen_inputs(*block_num).await?; 97 | 98 | Ok(Self { 99 | circuit_config, 100 | eth_block, 101 | block: builder.block, 102 | code_db: builder.code_db, 103 | }) 104 | } 105 | 106 | pub fn evm_witness(&self) -> zkevm_circuits::witness::Block { 107 | let mut block = 108 | evm_circuit::witness::block_convert(&self.block, &self.code_db).expect("block_convert"); 109 | block.exp_circuit_pad_to = self.circuit_config.pad_to; 110 | // fixed randomness used in PublicInput contract and SuperCircuit 111 | block.randomness = Fr::from(0x100); 112 | 113 | block 114 | } 115 | 116 | pub fn gas_used(&self) -> u64 { 117 | self.eth_block.gas_used.as_u64() 118 | } 119 | 120 | pub fn txs(&self) -> Vec { 121 | let txs = self 122 | .eth_block 123 | .transactions 124 | .iter() 125 | .map(geth_types::Transaction::from) 126 | .collect(); 127 | 128 | txs 129 | } 130 | 131 | pub fn public_data(&self) -> PublicData { 132 | let chain_id = self.block.chain_id; 133 | let eth_block = self.eth_block.clone(); 134 | let history_hashes = self.block.history_hashes.clone(); 135 | let block_constants = geth_types::BlockConstants { 136 | coinbase: eth_block.author.expect("coinbase"), 137 | timestamp: eth_block.timestamp, 138 | number: eth_block.number.expect("number"), 139 | difficulty: eth_block.difficulty, 140 | gas_limit: eth_block.gas_limit, 141 | base_fee: eth_block.base_fee_per_gas.unwrap_or_default(), 142 | }; 143 | let prev_state_root = H256::from(self.block.prev_state_root.to_be_bytes()); 144 | 145 | PublicData { 146 | chain_id, 147 | history_hashes, 148 | block_constants, 149 | prev_state_root, 150 | transactions: eth_block.transactions.clone(), 151 | // block_hash: eth_block.hash.unwrap_or_default(), 152 | state_root: eth_block.state_root, 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /prover/src/circuits.rs: -------------------------------------------------------------------------------- 1 | use crate::circuit_witness::CircuitWitness; 2 | use crate::Fr; 3 | use rand::Rng; 4 | use zkevm_circuits::bytecode_circuit::circuit::BytecodeCircuit; 5 | use zkevm_circuits::copy_circuit::CopyCircuit; 6 | use zkevm_circuits::evm_circuit::EvmCircuit; 7 | use zkevm_circuits::exp_circuit::ExpCircuit; 8 | use zkevm_circuits::keccak_circuit::KeccakCircuit; 9 | use zkevm_circuits::pi_circuit::PiCircuit; 10 | use zkevm_circuits::state_circuit::StateCircuit; 11 | use zkevm_circuits::super_circuit::SuperCircuit; 12 | use zkevm_circuits::tx_circuit::TxCircuit; 13 | use zkevm_circuits::util::SubCircuit; 14 | 15 | /// Returns a instance of the `SuperCircuit`. 16 | pub fn gen_super_circuit< 17 | const MAX_TXS: usize, 18 | const MAX_CALLDATA: usize, 19 | const MAX_RWS: usize, 20 | const MAX_COPY_ROWS: usize, 21 | RNG: Rng, 22 | >( 23 | witness: &CircuitWitness, 24 | mut _rng: RNG, 25 | ) -> Result, String> { 26 | let block = witness.evm_witness(); 27 | let circuit = SuperCircuit::new_from_block(&block); 28 | Ok(circuit) 29 | } 30 | 31 | /// Returns a instance of the `PiCircuit`. 32 | pub fn gen_pi_circuit< 33 | const MAX_TXS: usize, 34 | const MAX_CALLDATA: usize, 35 | const MAX_RWS: usize, 36 | const MAX_COPY_ROWS: usize, 37 | RNG: Rng, 38 | >( 39 | witness: &CircuitWitness, 40 | mut _rng: RNG, 41 | ) -> Result, String> { 42 | let block = witness.evm_witness(); 43 | let circuit = PiCircuit::new_from_block(&block); 44 | 45 | Ok(circuit) 46 | } 47 | 48 | /// Returns a instance of the `EvmCircuit`. 49 | pub fn gen_evm_circuit< 50 | const MAX_TXS: usize, 51 | const MAX_CALLDATA: usize, 52 | const MAX_RWS: usize, 53 | const MAX_COPY_ROWS: usize, 54 | RNG: Rng, 55 | >( 56 | witness: &CircuitWitness, 57 | mut _rng: RNG, 58 | ) -> Result, String> { 59 | let block = witness.evm_witness(); 60 | Ok(EvmCircuit::new_from_block(&block)) 61 | } 62 | 63 | /// Returns a instance of the `StateCircuit`. 64 | pub fn gen_state_circuit< 65 | const MAX_TXS: usize, 66 | const MAX_CALLDATA: usize, 67 | const MAX_RWS: usize, 68 | const MAX_COPY_ROWS: usize, 69 | RNG: Rng, 70 | >( 71 | witness: &CircuitWitness, 72 | mut _rng: RNG, 73 | ) -> Result, String> { 74 | let block = witness.evm_witness(); 75 | Ok(StateCircuit::new_from_block(&block)) 76 | } 77 | 78 | /// Returns a instance of the `TxCircuit`. 79 | pub fn gen_tx_circuit< 80 | const MAX_TXS: usize, 81 | const MAX_CALLDATA: usize, 82 | const MAX_RWS: usize, 83 | const MAX_COPY_ROWS: usize, 84 | RNG: Rng, 85 | >( 86 | witness: &CircuitWitness, 87 | mut _rng: RNG, 88 | ) -> Result, String> { 89 | let block = witness.evm_witness(); 90 | Ok(TxCircuit::new_from_block(&block)) 91 | } 92 | 93 | /// Returns a instance of the `BytecodeCircuit`. 94 | pub fn gen_bytecode_circuit< 95 | const MAX_TXS: usize, 96 | const MAX_CALLDATA: usize, 97 | const MAX_RWS: usize, 98 | const MAX_COPY_ROWS: usize, 99 | RNG: Rng, 100 | >( 101 | witness: &CircuitWitness, 102 | mut _rng: RNG, 103 | ) -> Result, String> { 104 | let block = witness.evm_witness(); 105 | Ok(BytecodeCircuit::new_from_block(&block)) 106 | } 107 | 108 | /// Returns a instance of the `CopyCircuit`. 109 | pub fn gen_copy_circuit< 110 | const MAX_TXS: usize, 111 | const MAX_CALLDATA: usize, 112 | const MAX_RWS: usize, 113 | const MAX_COPY_ROWS: usize, 114 | RNG: Rng, 115 | >( 116 | witness: &CircuitWitness, 117 | mut _rng: RNG, 118 | ) -> Result, String> { 119 | let block = witness.evm_witness(); 120 | Ok(CopyCircuit::new_from_block(&block)) 121 | } 122 | 123 | /// Returns a instance of the `ExpCircuit`. 124 | pub fn gen_exp_circuit< 125 | const MAX_TXS: usize, 126 | const MAX_CALLDATA: usize, 127 | const MAX_RWS: usize, 128 | const MAX_COPY_ROWS: usize, 129 | RNG: Rng, 130 | >( 131 | witness: &CircuitWitness, 132 | mut _rng: RNG, 133 | ) -> Result, String> { 134 | let block = witness.evm_witness(); 135 | Ok(ExpCircuit::new_from_block(&block)) 136 | } 137 | 138 | /// Returns a instance of the `KeccakCircuit`. 139 | pub fn gen_keccak_circuit< 140 | const MAX_TXS: usize, 141 | const MAX_CALLDATA: usize, 142 | const MAX_RWS: usize, 143 | const MAX_COPY_ROWS: usize, 144 | RNG: Rng, 145 | >( 146 | witness: &CircuitWitness, 147 | mut _rng: RNG, 148 | ) -> Result, String> { 149 | let block = witness.evm_witness(); 150 | Ok(KeccakCircuit::new_from_block(&block)) 151 | } 152 | -------------------------------------------------------------------------------- /prover/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use halo2_proofs::halo2curves::bn256::Bn256; 2 | pub use halo2_proofs::halo2curves::bn256::Fq; 3 | pub use halo2_proofs::halo2curves::bn256::Fr; 4 | pub use halo2_proofs::halo2curves::bn256::G1Affine; 5 | use halo2_proofs::plonk::ProvingKey; 6 | use halo2_proofs::poly::kzg::commitment::KZGCommitmentScheme; 7 | use halo2_proofs::poly::kzg::commitment::ParamsKZG; 8 | 9 | pub const VERSION: &str = env!("PROVER_VERSION"); 10 | 11 | pub type ProverParams = ParamsKZG; 12 | pub type ProverCommitmentScheme = KZGCommitmentScheme; 13 | pub type ProverKey = ProvingKey; 14 | 15 | pub mod circuit_autogen; 16 | pub mod circuit_witness; 17 | pub mod circuits; 18 | pub mod server; 19 | pub mod shared_state; 20 | pub mod utils; 21 | -------------------------------------------------------------------------------- /prover/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::Fr; 2 | use crate::G1Affine; 3 | use crate::ProverCommitmentScheme; 4 | use crate::ProverKey; 5 | use crate::ProverParams; 6 | use eth_types::U256; 7 | use halo2_proofs::dev::MockProver; 8 | use halo2_proofs::halo2curves::ff::PrimeField; 9 | use halo2_proofs::plonk::create_proof; 10 | use halo2_proofs::plonk::verify_proof; 11 | use halo2_proofs::plonk::Circuit; 12 | use halo2_proofs::poly::commitment::Params; 13 | use halo2_proofs::poly::commitment::ParamsProver; 14 | use halo2_proofs::poly::kzg::multiopen::ProverGWC; 15 | use halo2_proofs::poly::kzg::multiopen::VerifierGWC; 16 | use halo2_proofs::poly::kzg::strategy::SingleStrategy; 17 | use halo2_proofs::transcript::EncodedChallenge; 18 | use halo2_proofs::transcript::TranscriptReadBuffer; 19 | use halo2_proofs::transcript::TranscriptWriterBuffer; 20 | use rand::rngs::StdRng; 21 | use rand::Rng; 22 | use rand::SeedableRng; 23 | use std::clone::Clone; 24 | use std::io::Cursor; 25 | use std::time::Instant; 26 | use zkevm_common::prover::ProofResultInstrumentation; 27 | 28 | /// Returns [, ...] of `instance` 29 | pub fn gen_num_instance(instance: &[Vec]) -> Vec { 30 | instance.iter().map(|v| v.len()).collect() 31 | } 32 | 33 | /// Returns the finalized transcript. 34 | /// Runs the MockProver on `create_proof` error and panics afterwards. 35 | #[allow(clippy::too_many_arguments)] 36 | pub fn gen_proof< 37 | C: Circuit + Clone, 38 | E: EncodedChallenge, 39 | TR: TranscriptReadBuffer>, G1Affine, E>, 40 | TW: TranscriptWriterBuffer, G1Affine, E>, 41 | RNG: Rng, 42 | >( 43 | params: &ProverParams, 44 | pk: &ProverKey, 45 | circuit: C, 46 | instance: Vec>, 47 | rng: RNG, 48 | mock_feedback: bool, 49 | verify: bool, 50 | aux: &mut ProofResultInstrumentation, 51 | ) -> Vec { 52 | let mut transcript = TW::init(Vec::new()); 53 | let inputs: Vec<&[Fr]> = instance.iter().map(|v| v.as_slice()).collect(); 54 | let res = { 55 | let time_started = Instant::now(); 56 | let v = create_proof::, _, _, TW, _>( 57 | params, 58 | pk, 59 | &[circuit.clone()], 60 | &[inputs.as_slice()], 61 | rng, 62 | &mut transcript, 63 | ); 64 | aux.proof = Instant::now().duration_since(time_started).as_millis() as u32; 65 | v 66 | }; 67 | // run the `MockProver` and return (hopefully) useful errors 68 | if let Err(proof_err) = res { 69 | if mock_feedback { 70 | let res = { 71 | let time_started = Instant::now(); 72 | let v = MockProver::run(params.k(), &circuit, instance) 73 | .expect("MockProver::run") 74 | .verify_par(); 75 | aux.mock = Instant::now().duration_since(time_started).as_millis() as u32; 76 | v 77 | }; 78 | panic!("gen_proof: {proof_err:#?}\nMockProver: {res:#?}"); 79 | } else { 80 | panic!("gen_proof: {proof_err:#?}"); 81 | } 82 | } 83 | 84 | let proof = transcript.finalize(); 85 | if verify { 86 | let mut transcript = TR::init(Cursor::new(proof.clone())); 87 | let res = { 88 | let time_started = Instant::now(); 89 | let v = verify_proof::<_, VerifierGWC<_>, _, TR, _>( 90 | params.verifier_params(), 91 | pk.get_vk(), 92 | SingleStrategy::new(params.verifier_params()), 93 | &[inputs.as_slice()], 94 | &mut transcript, 95 | ); 96 | aux.verify = Instant::now().duration_since(time_started).as_millis() as u32; 97 | v 98 | }; 99 | 100 | if let Err(verify_err) = res { 101 | if mock_feedback { 102 | let res = { 103 | let time_started = Instant::now(); 104 | let v = MockProver::run(params.k(), &circuit, instance) 105 | .expect("MockProver::run") 106 | .verify_par(); 107 | aux.mock = Instant::now().duration_since(time_started).as_millis() as u32; 108 | v 109 | }; 110 | panic!("verify_proof: {verify_err:#?}\nMockProver: {res:#?}"); 111 | } else { 112 | panic!("verify_proof: {verify_err:#?}"); 113 | } 114 | } 115 | } 116 | 117 | proof 118 | } 119 | 120 | /// Fixed rng for testing purposes 121 | pub fn fixed_rng() -> StdRng { 122 | StdRng::seed_from_u64(9) 123 | } 124 | 125 | /// Collect circuit instance as flat vector 126 | pub fn collect_instance(instance: &[Vec]) -> Vec { 127 | instance 128 | .iter() 129 | .flatten() 130 | .map(|v| U256::from_little_endian(v.to_repr().as_ref())) 131 | .collect() 132 | } 133 | -------------------------------------------------------------------------------- /prover/tests/proverd.rs: -------------------------------------------------------------------------------- 1 | use prover::server::serve; 2 | use prover::shared_state::SharedState; 3 | use tokio::time::{sleep, Duration}; 4 | use zkevm_common::prover::*; 5 | 6 | fn init_logger() { 7 | let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")) 8 | .is_test(true) 9 | .try_init(); 10 | } 11 | 12 | #[tokio::test] 13 | #[allow(clippy::let_underscore_future)] 14 | async fn proverd_simple_signaling() { 15 | init_logger(); 16 | 17 | let node_a = SharedState::new("a".to_string(), Some("127.0.0.1:11111".to_string())); 18 | let node_b = SharedState::new("b".to_string(), Some("127.0.0.1:11112".to_string())); 19 | // start http servers 20 | { 21 | let _ = serve(&node_a, node_b.ro.node_lookup.as_ref().unwrap()); 22 | let _ = serve(&node_b, node_a.ro.node_lookup.as_ref().unwrap()); 23 | } 24 | 25 | // wait a bit for the rpc server to start 26 | sleep(Duration::from_millis(300)).await; 27 | 28 | let proof_a = ProofRequestOptions { 29 | circuit: "super".to_string(), 30 | block: 1, 31 | retry: false, 32 | rpc: "http://localhost:1111".to_string(), 33 | ..Default::default() 34 | }; 35 | let proof_b = ProofRequestOptions { 36 | circuit: "super".to_string(), 37 | block: 2, 38 | retry: false, 39 | rpc: "http://localhost:1111".to_string(), 40 | ..Default::default() 41 | }; 42 | 43 | // enqueue tasks 44 | assert!(node_a.get_or_enqueue(&proof_a).await.is_none()); 45 | assert!(node_b.get_or_enqueue(&proof_b).await.is_none()); 46 | 47 | // start work on node_a 48 | node_a.duty_cycle().await; 49 | assert!(node_a.get_or_enqueue(&proof_a).await.is_some()); 50 | 51 | // node_b didn't sync yet 52 | assert!(node_b.get_or_enqueue(&proof_a).await.is_none()); 53 | // sync, do work 54 | let _ = node_b.merge_tasks_from_peers().await; 55 | // check again 56 | assert!(node_b.get_or_enqueue(&proof_a).await.is_some()); 57 | 58 | // no result yet 59 | assert!(node_b.get_or_enqueue(&proof_b).await.is_none()); 60 | // sync, do work 61 | node_b.duty_cycle().await; 62 | // check again 63 | assert!(node_b.get_or_enqueue(&proof_b).await.is_some()); 64 | 65 | // node_a didn't sync yet 66 | assert!(node_a.get_or_enqueue(&proof_b).await.is_none()); 67 | // sync node_a 68 | let _ = node_a.merge_tasks_from_peers().await; 69 | // check again 70 | assert!(node_a.get_or_enqueue(&proof_b).await.is_some()); 71 | } 72 | -------------------------------------------------------------------------------- /prover/tests/verifier.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "autogen")] 2 | 3 | use eth_types::Address; 4 | use halo2_proofs::circuit::Value; 5 | use halo2_proofs::plonk::keygen_vk; 6 | use halo2_proofs::plonk::VerifyingKey; 7 | use halo2_proofs::poly::commitment::ParamsProver; 8 | use paste::paste; 9 | use prover::circuit_witness::CircuitWitness; 10 | use prover::circuits::*; 11 | use prover::utils::fixed_rng; 12 | use prover::utils::gen_num_instance; 13 | use prover::Bn256; 14 | use prover::ProverParams; 15 | use prover::{Fq, Fr, G1Affine}; 16 | use snark_verifier::loader::evm::EvmLoader; 17 | use snark_verifier::{ 18 | system::halo2::{compile, transcript::evm::EvmTranscript, Config}, 19 | verifier::SnarkVerifier, 20 | }; 21 | use std::env::var; 22 | use std::fs; 23 | use std::io::Write; 24 | use std::rc::Rc; 25 | use zkevm_circuits::root_circuit::KzgDk; 26 | use zkevm_circuits::root_circuit::KzgSvk; 27 | use zkevm_circuits::root_circuit::PlonkVerifier; 28 | use zkevm_circuits::root_circuit::RootCircuit; 29 | use zkevm_circuits::util::SubCircuit; 30 | use zkevm_common::prover::*; 31 | 32 | #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)] 33 | struct Verifier { 34 | label: String, 35 | config: CircuitConfig, 36 | code: String, 37 | address: Address, 38 | } 39 | 40 | impl Verifier { 41 | fn build(&mut self) -> &Self { 42 | let mut tmp = [0; 20]; 43 | let bytes = self.label.as_bytes(); 44 | let x = 20 - bytes.len(); 45 | for (i, v) in bytes.iter().enumerate() { 46 | tmp[i + x] = *v; 47 | } 48 | self.address = Address::from(tmp); 49 | 50 | self 51 | } 52 | 53 | fn write_yul(&mut self) -> &Self { 54 | self.build(); 55 | let file_name = format!("verifier-{}-{:?}.yul", self.label, self.address); 56 | // only keep the runtime section 57 | let yul_code = format!("object \"{}\" ", self.label) 58 | + self.code.split("object \"Runtime\"").last().unwrap(); 59 | // strip of the dangling `}` 60 | let yul_code = &yul_code[0..yul_code.len() - 1]; 61 | write_bytes(&file_name, yul_code.as_bytes()); 62 | 63 | self 64 | } 65 | } 66 | 67 | fn write_bytes(name: &str, vec: &[u8]) { 68 | let dir = "./../contracts/generated/"; 69 | fs::create_dir_all(dir).unwrap_or_else(|_| panic!("create {dir}")); 70 | let path = format!("{dir}/{name}"); 71 | fs::File::create(&path) 72 | .unwrap_or_else(|_| panic!("create {}", &path)) 73 | .write_all(vec) 74 | .unwrap_or_else(|_| panic!("write {}", &path)); 75 | } 76 | 77 | fn gen_verifier( 78 | params: &ProverParams, 79 | vk: &VerifyingKey, 80 | config: Config, 81 | num_instance: Vec, 82 | ) -> String { 83 | let protocol = compile(params, vk, config); 84 | let svk = KzgSvk::::new(params.get_g()[0]); 85 | let dk = KzgDk::::new(svk, params.g2(), params.s_g2()); 86 | 87 | let loader = EvmLoader::new::(); 88 | let protocol = protocol.loaded(&loader); 89 | let mut transcript = EvmTranscript::<_, Rc, _, _>::new(&loader); 90 | 91 | let instances = transcript.load_instances(num_instance); 92 | let proof = PlonkVerifier::read_proof(&dk, &protocol, &instances, &mut transcript).unwrap(); 93 | PlonkVerifier::verify(&dk, &protocol, &instances, &proof).unwrap(); 94 | 95 | loader.yul_code() 96 | } 97 | 98 | macro_rules! gen_match { 99 | ($LABEL:expr, $CIRCUIT:ident, $GAS:expr) => {{ 100 | let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) 101 | .try_init(); 102 | 103 | prover::match_circuit_params!( 104 | $GAS, 105 | { 106 | let protocol = { 107 | let witness = CircuitWitness::dummy(CIRCUIT_CONFIG).unwrap(); 108 | let circuit = $CIRCUIT::< 109 | { CIRCUIT_CONFIG.max_txs }, 110 | { CIRCUIT_CONFIG.max_calldata }, 111 | { CIRCUIT_CONFIG.max_rws }, 112 | { CIRCUIT_CONFIG.max_copy_rows }, 113 | _, 114 | >(&witness, fixed_rng()) 115 | .expect("gen_static_circuit"); 116 | let params = ProverParams::setup(CIRCUIT_CONFIG.min_k as u32, fixed_rng()); 117 | let vk = keygen_vk(¶ms, &circuit).expect("vk"); 118 | let instance = circuit.instance(); 119 | 120 | { 121 | let mut data = Verifier::default(); 122 | data.label = format!("{}-{}", $LABEL, CIRCUIT_CONFIG.block_gas_limit); 123 | data.config = CIRCUIT_CONFIG; 124 | data.code = gen_verifier( 125 | ¶ms, 126 | &vk, 127 | Config::kzg().with_num_instance(gen_num_instance(&instance)), 128 | gen_num_instance(&instance), 129 | ) 130 | .into(); 131 | data.write_yul(); 132 | 133 | if var("ONLY_EVM").is_ok() { 134 | log::info!("returning early"); 135 | return; 136 | } 137 | } 138 | 139 | let protocol = compile( 140 | ¶ms, 141 | &vk, 142 | Config::kzg().with_num_instance(gen_num_instance(&instance)), 143 | ); 144 | 145 | protocol 146 | }; 147 | 148 | let agg_params = 149 | ProverParams::setup(CIRCUIT_CONFIG.min_k_aggregation as u32, fixed_rng()); 150 | let agg_circuit = 151 | RootCircuit::new(&agg_params, &protocol, Value::unknown(), Value::unknown()) 152 | .expect("RootCircuit::new"); 153 | 154 | let agg_vk = keygen_vk(&agg_params, &agg_circuit).expect("vk"); 155 | 156 | let mut data = Verifier::default(); 157 | data.label = format!("{}-{}-a", $LABEL, CIRCUIT_CONFIG.block_gas_limit); 158 | data.config = CIRCUIT_CONFIG; 159 | data.code = gen_verifier( 160 | &agg_params, 161 | &agg_vk, 162 | Config::kzg() 163 | .with_num_instance(agg_circuit.num_instance()) 164 | .with_accumulator_indices(Some(agg_circuit.accumulator_indices())), 165 | agg_circuit.num_instance(), 166 | ) 167 | .into(); 168 | data.write_yul(); 169 | }, 170 | { 171 | panic!("no circuit parameters found"); 172 | } 173 | ); 174 | }}; 175 | } 176 | 177 | macro_rules! gen_test_fn { 178 | ($LABEL:expr, $CIRCUIT:ident, $GAS:expr) => { 179 | paste! { 180 | #[test] 181 | fn []() { 182 | gen_match!($LABEL, $CIRCUIT, $GAS); 183 | } 184 | } 185 | }; 186 | } 187 | 188 | macro_rules! for_each { 189 | ($LABEL:expr, $CIRCUIT:ident) => { 190 | gen_test_fn!($LABEL, $CIRCUIT, 63_000); 191 | gen_test_fn!($LABEL, $CIRCUIT, 300_000); 192 | }; 193 | } 194 | 195 | for_each!("super", gen_super_circuit); 196 | for_each!("pi", gen_pi_circuit); 197 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2023-01-21 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | edition = "2021" 3 | -------------------------------------------------------------------------------- /scripts/autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | cargo test --release --features autogen -- --nocapture $@ 6 | -------------------------------------------------------------------------------- /scripts/ci_autogen_commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | if [ -z "$CI" ]; then 6 | exit 1 7 | fi 8 | 9 | git config user.email 'github-actions@github.com' 10 | git config user.name github-actions 11 | git commit -am 'updates from autogen workflow' || exit 0 12 | if [ "${GITHUB_REF_PROTECTED}" = "true" ]; then 13 | git push origin "autogen-$(git rev-parse HEAD)" 14 | else 15 | git push 16 | fi 17 | -------------------------------------------------------------------------------- /scripts/ci_commit_errors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | if [ ! -d errors/ ]; then 6 | exit 0 7 | fi 8 | 9 | if [ -z "$CI" ]; then 10 | exit 1 11 | fi 12 | 13 | sudo chown -R $(id -nu) errors/ 14 | 15 | git config user.email 'github-actions@github.com' 16 | git config user.name github-actions 17 | name=${1:-unknown} 18 | branch=prover-error-"$1"-$(git rev-parse HEAD) 19 | git checkout -b $branch && git add errors/ && git commit -m 'add prover errors' && git push origin $branch 20 | 21 | git reset --hard HEAD^ 22 | # exit with error to signal prover failure 23 | exit 1 24 | -------------------------------------------------------------------------------- /scripts/ci_grafana.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | # data collection 6 | TEST_DATE=$(date -uR) 7 | PROVER_VERSION=$(cat PROVER_VERSION.txt) 8 | ELAPSED_TIME=$(cat PROVER_STATS.txt | awk -F ': ' 'FNR==5 {print $2}') 9 | NUM_CPUS=$(cat /proc/cpuinfo | grep processor | wc -l) 10 | CPU_PERCENT=$(cat PROVER_STATS.txt | awk -F ': ' "FNR==4 {print \$2 / $NUM_CPUS}") 11 | MEM_MAX_MB=$(cat PROVER_STATS.txt | awk -F ': ' 'FNR==10 {print $2 "/ 1024"}' | bc) 12 | PAGE_FAULTS=$(cat PROVER_STATS.txt | awk -F ': ' 'FNR==13 {print $2}') 13 | PAGE_SIZE=$(cat PROVER_STATS.txt | awk -F ': ' 'FNR==22 {print $2}') 14 | 15 | # assuming only 1 prover task 16 | TASK=$(cat PROVER_DATA.txt | jq -c '.tasks[0]') 17 | 18 | ERROR_URL="https://github.com/${GITHUB_REPOSITORY}/tree/prover-error-${TEST_ID}/errors-$(git -c safe.directory='*' rev-parse HEAD)" 19 | ERROR_DESCRIPTION=$(printf '%s' "${TASK}" | jq -cr '.result.Err[0:255]') 20 | CIRCUIT_LABEL=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.label') 21 | CIRCUIT_MS_INIT=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.aux.circuit') 22 | CIRCUIT_MS_VK=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.aux.vk') 23 | CIRCUIT_MS_PK=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.aux.pk') 24 | CIRCUIT_MS_PROOF=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.aux.proof') 25 | CIRCUIT_MS_VERIFY=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.aux.verify') 26 | CIRCUIT_MS_MOCK=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.circuit.aux.mock') 27 | AGGREGATE_LABEL=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.label') 28 | AGGREGATE_MS_INIT=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.aux.circuit') 29 | AGGREGATE_MS_VK=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.aux.vk') 30 | AGGREGATE_MS_PK=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.aux.pk') 31 | AGGREGATE_MS_PROOF=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.aux.proof') 32 | AGGREGATE_MS_VERIFY=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.aux.verify') 33 | AGGREGATE_MS_PROTOCOL=$(printf '%s' "${TASK}" | jq -cr '.result.Ok.aggregation.aux.protocol') 34 | DUMMY_FLAG='false' 35 | 36 | SQL=$(cat << EOF 37 | INSERT INTO testresults_zkevm_chain_integration_github ( 38 | test_name, 39 | date, 40 | github_ref, 41 | prover_version, 42 | elapsed_time, 43 | num_cpus, 44 | cpu_percent, 45 | mem_max_mb, 46 | page_faults, 47 | page_size, 48 | error_description, 49 | error_url, 50 | circuit_label, 51 | circuit_ms_init, 52 | circuit_ms_vk, 53 | circuit_ms_pk, 54 | circuit_ms_proof, 55 | circuit_ms_verify, 56 | circuit_ms_mock, 57 | aggregation_label, 58 | aggregation_ms_init, 59 | aggregation_ms_vk, 60 | aggregation_ms_pk, 61 | aggregation_ms_proof, 62 | aggregation_ms_verify, 63 | aggregation_ms_protocol, 64 | dummy 65 | ) 66 | VALUES ( 67 | '${TEST_NAME}', 68 | '${TEST_DATE}', 69 | '${GITHUB_REF}', 70 | '${PROVER_VERSION}', 71 | '${ELAPSED_TIME}', 72 | ${NUM_CPUS}, 73 | ${CPU_PERCENT}, 74 | ${MEM_MAX_MB}, 75 | ${PAGE_FAULTS}, 76 | ${PAGE_SIZE}, 77 | '${ERROR_DESCRIPTION}', 78 | '${ERROR_URL}', 79 | '${CIRCUIT_LABEL}', 80 | ${CIRCUIT_MS_INIT}, 81 | ${CIRCUIT_MS_VK}, 82 | ${CIRCUIT_MS_PK}, 83 | ${CIRCUIT_MS_PROOF}, 84 | ${CIRCUIT_MS_VERIFY}, 85 | ${CIRCUIT_MS_MOCK}, 86 | '${AGGREGATE_LABEL}', 87 | ${AGGREGATE_MS_INIT}, 88 | ${AGGREGATE_MS_VK}, 89 | ${AGGREGATE_MS_PK}, 90 | ${AGGREGATE_MS_PROOF}, 91 | ${AGGREGATE_MS_VERIFY}, 92 | ${AGGREGATE_MS_PROTOCOL}, 93 | ${DUMMY_FLAG} 94 | ); 95 | EOF 96 | ) 97 | 98 | echo "${SQL}" 99 | -------------------------------------------------------------------------------- /scripts/compile_contracts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT="$(dirname $0)/.." 5 | 6 | for file in $(find "$ROOT"/contracts/templates/ -iname '*.sol'); do 7 | name=$(basename $file) 8 | generated="$ROOT"/contracts/generated/$name 9 | cp "$file" "$generated" 10 | for inc in $(cat "$file" | grep "//@INCLUDE:"); do 11 | template=$(echo "$inc" | awk -F":" '{print $NF}') 12 | sed -i -e "\#$inc#r $ROOT/contracts/templates/$template" "$generated" 13 | done 14 | done 15 | 16 | OUTPUT_PATH="$ROOT/build/contracts" 17 | mkdir -p "$OUTPUT_PATH" 18 | 19 | SOLC=$(which solc || printf '%s' "docker run --rm -w /app -v $(pwd):/app ethereum/solc:0.8.16") 20 | # solidity 21 | $SOLC \ 22 | --evm-version berlin \ 23 | --no-cbor-metadata \ 24 | --metadata-hash none \ 25 | --combined-json bin,bin-runtime,srcmap,srcmap-runtime,storage-layout \ 26 | --optimize \ 27 | --optimize-runs 4294967295 \ 28 | --overwrite \ 29 | -o "$OUTPUT_PATH" \ 30 | $(find "$ROOT"/contracts/ -iname '*.sol' | grep -v templates/) 31 | # generated yul 32 | mkdir -p "${OUTPUT_PATH}/plonk-verifier" 33 | for file in $(find "$ROOT"/contracts/generated/ -iname '*.yul'); do 34 | name=$(basename "${file}" | head -c -5) 35 | $SOLC \ 36 | --yul \ 37 | --bin \ 38 | "${file}" | tail -n 1 | tr -d \\n > "${OUTPUT_PATH}/plonk-verifier/${name}" 39 | done 40 | -------------------------------------------------------------------------------- /scripts/dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | DOCKER_BUILDKIT=1 docker compose up -d dev 6 | docker compose exec dev bash 7 | -------------------------------------------------------------------------------- /scripts/get_block_fixtures.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # usage rpc_url block_number 3 | url=$1 4 | blockNumber=$2 5 | 6 | set -e 7 | 8 | rpc() { 9 | curl \ 10 | --silent \ 11 | -H 'content-type: application/json' \ 12 | -d '{"id":0, "jsonrpc":"2.0","method":"'$1'", "params":['$2']}' \ 13 | $url 14 | } 15 | 16 | hex() { 17 | printf '0x%x' $1 18 | } 19 | 20 | blockHex=$(hex $blockNumber) 21 | 22 | echo '### block.json' 23 | rpc 'eth_getBlockByNumber' '"'$blockHex'",true' | jq '.result' > block.json 24 | 25 | echo '### prestate.json' 26 | rpc 'debug_traceBlockByNumber' '"'$blockHex'",{"tracer":"prestateTracer"}' | jq '.result' > prestate.json 27 | 28 | echo '### block_hashes.json' 29 | stop=$(($blockNumber - 1)) 30 | start=$(($stop - 255)) 31 | tmp='[' 32 | for num in $(seq $start $stop); do 33 | if [ $num -lt 0 ]; then 34 | tmp=$tmp'"0x0000000000000000000000000000000000000000000000000000000000000000",' 35 | continue 36 | fi 37 | blockHex=$(hex $num) 38 | hash=$(rpc 'eth_getHeaderByNumber' '"'$blockHex'"' | jq '.result.hash') 39 | tmp=$tmp$hash',' 40 | done 41 | tmp=$(printf -- $tmp | head -c -1) 42 | tmp="$tmp]" 43 | echo "$tmp" | jq > block_hashes.json 44 | 45 | -------------------------------------------------------------------------------- /scripts/heavy_ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | rm -rf errors 6 | pkill -9 prover_rpcd || true 7 | 8 | trap 'pkill --parent $$' TERM EXIT INT 9 | 10 | ./scripts/compile_contracts.sh 11 | 12 | cargo build --release --bin prover_rpcd 13 | env time --quiet --output PROVER_STATS.txt --verbose -- \ 14 | cargo run --release --bin prover_rpcd 2>&1 | xz > PROVER_LOG.txt.xz & 15 | PID=$! 16 | perf stat --pid $PID -I 300000 -o PROVER_PERF.txt \ 17 | -e stalled-cycles-backend \ 18 | -e stalled-cycles-frontend \ 19 | -e instructions \ 20 | -e branch-instructions \ 21 | -e ic_fetch_stall.ic_stall_any \ 22 | -e ic_fetch_stall.ic_stall_back_pressure \ 23 | -e ic_fetch_stall.ic_stall_dq_empty \ 24 | -e sse_avx_stalls \ 25 | -e all_data_cache_accesses \ 26 | -e all_tlbs_flushed \ 27 | -e l1_data_cache_fills_all \ 28 | -e fp_ret_sse_avx_ops.all \ 29 | -e l1_data_cache_fills_all \ 30 | -e l2_cache_accesses_from_dc_misses \ 31 | -e l2_cache_accesses_from_ic_misses \ 32 | -e ic_tag_hit_miss.all_instruction_cache_accesses \ 33 | -e ic_tag_hit_miss.instruction_cache_hit \ 34 | -e ic_tag_hit_miss.instruction_cache_miss & 35 | 36 | # sleep a bit in case the geth nodes are not up yet 37 | sleep 3 38 | 39 | # finalize any leftover blocks 40 | COORDINATOR_DUMMY_PROVER=true cargo test -p coordinator -- finalize_chain --ignored || exit 1 41 | 42 | # now run all default tests 43 | COORDINATOR_DUMMY_PROVER=false cargo test -p coordinator -- $@ 44 | status=$? 45 | PROVER_DATA=$(./scripts/rpc_prover.sh info | jq -cr '.result') 46 | FAILED_BLOCKS=$(printf '%s' "${PROVER_DATA}" | jq -cr '.tasks | map(select(.result.Err)) | map(.options.block) | .[]') 47 | 48 | pkill -9 prover_rpcd || true 49 | wait $PID 50 | cargo run --release --bin prover_rpcd -- --version > PROVER_VERSION.txt 51 | cat PROVER_STATS.txt 52 | cat PROVER_PERF.txt 53 | printf '%s' "${PROVER_DATA}" > PROVER_DATA.txt 54 | 55 | if [ $status -eq 0 ]; then 56 | exit 0 57 | fi 58 | 59 | # if there are not failed proof requests, then something else failed 60 | if [ "${FAILED_BLOCKS}" = "" ]; then 61 | exit 1 62 | fi 63 | 64 | # error collection 65 | mkdir errors 66 | 67 | for block_num in $FAILED_BLOCKS; do 68 | ./scripts/get_block_fixtures.sh $COORDINATOR_L2_RPC_URL $block_num 69 | mkdir -p errors/$block_num 70 | mv block_hashes.json block.json prestate.json errors/$block_num/ 71 | done 72 | 73 | mv PROVER_LOG.txt.xz errors/ 74 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo fmt --all 4 | cargo clippy --all-features --all-targets 5 | -------------------------------------------------------------------------------- /scripts/patch_genesis.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | 5 | function pad (n, v) { 6 | return v.toString(16).padStart(n, '0'); 7 | } 8 | 9 | function getCode (name) { 10 | for (const id in artifacts) { 11 | if (id.split(':')[1] === name) { 12 | return '0x' + artifacts[id]['bin-runtime']; 13 | } 14 | } 15 | 16 | throw new Error(`${name} not found`); 17 | } 18 | 19 | const artifacts = JSON.parse(fs.readFileSync('./build/contracts/combined.json')).contracts; 20 | const L1_CONTRACTS = { 21 | '936a70c0b28532aa22240dce21f89a8399d6ac60': 'ZkEvmL1Bridge', 22 | '936a70c0b28532aa22240dce21f89a8399d6ac61': 'L1OptimismBridge', 23 | }; 24 | const baseAddress = BigInt('0x1111111111111111111111111111111111111111'); 25 | const path = './build/contracts/plonk-verifier'; 26 | if (fs.existsSync(path)) { 27 | for (const file of fs.readdirSync(path)) { 28 | const runtime_code = '0x' + fs.readFileSync(`${path}/${file}`); 29 | const addr = pad(40, BigInt(file.split('-').pop())); 30 | console.log({file, addr}); 31 | if (L1_CONTRACTS[addr]) { 32 | throw Error('exists'); 33 | } 34 | L1_CONTRACTS[addr] = { name: file, code: runtime_code }; 35 | } 36 | } 37 | 38 | const L2_CONTRACTS = { 39 | '0000000000000000000000000000000000010000': 'ZkEvmL2MessageDeliverer', 40 | '0000000000000000000000000000000000020000': 'ZkEvmL2MessageDispatcher', 41 | '4200000000000000000000000000000000000007': 'L2OptimisimBridge', 42 | }; 43 | const L1_TEMPLATE_PATH = 'docker/geth/templates/l1-testnet.json'; 44 | const L2_TEMPLATE_PATH = 'docker/geth/templates/l2-testnet.json'; 45 | 46 | const PROXY_CODE = getCode('Proxy'); 47 | const PROXY_SLOT = pad(64, BigInt.asUintN(256, '-1')); 48 | 49 | const OBJS = [ 50 | [L1_TEMPLATE_PATH, L1_CONTRACTS], 51 | [L2_TEMPLATE_PATH, L2_CONTRACTS], 52 | ]; 53 | 54 | for (const [path, contracts] of OBJS) { 55 | const genesis = JSON.parse(fs.readFileSync(path)); 56 | 57 | for (const addr in contracts) { 58 | let code; 59 | let name; 60 | let value = contracts[addr]; 61 | if (typeof value === 'string') { 62 | code = getCode(value); 63 | name = value; 64 | } else { 65 | code = value.code; 66 | name = value.name; 67 | } 68 | 69 | const proxy = genesis.alloc[addr] || { balance: '0' }; 70 | proxy.comment = 'Proxy:' + name; 71 | proxy.code = PROXY_CODE; 72 | proxy.storage = proxy.storage || {}; 73 | const implAddr = pad(40, BigInt.asUintN(160, BigInt('0x' + addr) + 0xcbaf2257000313ba2574n)); 74 | proxy.storage[PROXY_SLOT] = implAddr; 75 | genesis.alloc[addr] = proxy; 76 | 77 | genesis.alloc[implAddr] = { 78 | comment: name, 79 | balance: '0', 80 | code 81 | }; 82 | } 83 | 84 | fs.writeFileSync(path, JSON.stringify(genesis, null, 2)); 85 | } 86 | -------------------------------------------------------------------------------- /scripts/purge_chain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | docker compose down 6 | docker volume rm zkevm-chain_l1-testnet-geth zkevm-chain_leader-testnet-geth zkevm-chain_bootnode 7 | -------------------------------------------------------------------------------- /scripts/rpc_prover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | curl \ 6 | -H 'content-type: application/json' \ 7 | -d '{"id":0, "jsonrpc":"2.0","method":"'$1'", "params":['$2']}' \ 8 | "$PROVERD_LOOKUP" 9 | -------------------------------------------------------------------------------- /scripts/test_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | COORDINATOR_L1_RPC_URL=http://coverage-l1:8545 6 | COORDINATOR_L2_RPC_URL=http://coverage-l2:8545 7 | 8 | curl -f "$COORDINATOR_L1_RPC_URL"/reload 9 | curl -f "$COORDINATOR_L2_RPC_URL"/reload 10 | 11 | cargo test "$@" 12 | 13 | curl -f "$COORDINATOR_L1_RPC_URL"/.lcov > build/coverage-report.lcov 14 | curl -f "$COORDINATOR_L2_RPC_URL"/.lcov >> build/coverage-report.lcov 15 | -------------------------------------------------------------------------------- /scripts/test_prover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | trap 'pkill --parent $$' TERM EXIT INT 6 | 7 | pkill -9 prover_rpcd || true 8 | 9 | cargo build --release --bin prover_rpcd 10 | 11 | if [ "${DEV_PERF}" ]; then 12 | perf record -e task-clock -F 1000 -g -- cargo run --release --bin prover_rpcd > PROVER_LOG.txt 2>&1 & 13 | else 14 | cargo run --release --bin prover_rpcd > PROVER_LOG.txt 2>&1 & 15 | fi 16 | 17 | COORDINATOR_DUMMY_PROVER=false cargo test -- $@ 18 | --------------------------------------------------------------------------------